From 7c8d3f1ad95dd560468df06f2622af8de6bbda38 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 5 Jan 2021 10:37:41 +0800 Subject: [PATCH 001/179] Fix bug when modify column on mssql (#1849) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1849 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dialect.go | 2 +- dialects/mssql.go | 5 +++++ integrations/session_schema_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index dc96f73a..18b781a1 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -163,7 +163,7 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, false) - return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, s) + return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s) } func (b *Base) ForUpdateSQL(query string) string { diff --git a/dialects/mssql.go b/dialects/mssql.go index 8e76e538..5340455d 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -368,6 +368,11 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) { "DROP TABLE \"%s\"", tableName, tableName), true } +func (db *mssql) ModifyColumnSQL(tableName string, col *schemas.Column) string { + s, _ := ColumnString(db.dialect, col, false) + return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", tableName, s) +} + func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 3f8f810b..72779e11 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/schemas" ) func TestStoreEngine(t *testing.T) { @@ -396,3 +397,26 @@ func TestSync2_Default(t *testing.T) { assertSync(t, new(TestSync2Default)) assert.NoError(t, testEngine.Sync2(new(TestSync2Default))) } + +func TestModifyColum(t *testing.T) { + type TestModifyColumn struct { + Id int64 + UserId int64 `xorm:"default(1)"` + IsMember bool `xorm:"default(true)"` + Name string `xorm:"char(10)"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(TestModifyColumn)) + + alterSQL := testEngine.Dialect().ModifyColumnSQL("test_modify_column", &schemas.Column{ + Name: "name", + SQLType: schemas.SQLType{ + Name: "VARCHAR", + }, + Length: 16, + Nullable: false, + }) + _, err := testEngine.Exec(alterSQL) + assert.NoError(t, err) +} From b65276da851d1773281b7129049495e28e070608 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 5 Jan 2021 10:42:14 +0800 Subject: [PATCH 002/179] Fix index (#1841) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1841 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/sqlite3.go | 2 +- engine.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 73f98beb..62a38397 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -483,7 +483,7 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa continue } - indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []'\"") + indexName := strings.Trim(strings.TrimSpace(sql[nNStart+6:nNEnd]), "`[]'\"") var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { index.Name = indexName[5+len(tableName):] diff --git a/engine.go b/engine.go index 6c894e74..873fcdc1 100644 --- a/engine.go +++ b/engine.go @@ -359,13 +359,16 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { var seq int for _, index := range indexes { for _, name := range index.Cols { - parts := strings.Split(name, " ") + parts := strings.Split(strings.TrimSpace(name), " ") if len(parts) > 1 { if parts[1] == "DESC" { seq = 1 + } else if parts[1] == "ASC" { + seq = 0 } } - if col := table.GetColumn(parts[0]); col != nil { + var colName = strings.Trim(parts[0], `"`) + if col := table.GetColumn(colName); col != nil { col.Indexes[index.Name] = index.Type } else { return fmt.Errorf("Unknown col %s seq %d, in index %v of table %v, columns %v", name, seq, index.Name, table.Name, table.ColumnsSeq()) From 26b248c5691f19a62ed607dfaaca81fd15671f1b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 5 Jan 2021 10:55:36 +0800 Subject: [PATCH 003/179] Fix update bug (#1823) Fix #1821 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1823 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_schema_test.go | 4 ++++ integrations/session_update_test.go | 14 ++++++++++++++ session_update.go | 9 ++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 72779e11..1ef653ef 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -399,6 +399,10 @@ func TestSync2_Default(t *testing.T) { } func TestModifyColum(t *testing.T) { + // Since SQLITE don't support modify column SQL, currrently just ignore + if testEngine.Dialect().URI().DBType == schemas.SQLITE { + return + } type TestModifyColumn struct { Id int64 UserId int64 `xorm:"default(1)"` diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 07c722bd..f2105e2b 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -15,6 +15,7 @@ import ( "xorm.io/xorm/internal/statements" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) func TestUpdateMap(t *testing.T) { @@ -48,6 +49,19 @@ func TestUpdateMap(t *testing.T) { assert.Error(t, err) assert.True(t, statements.IsIDConditionWithNoTableErr(err)) assert.EqualValues(t, 0, cnt) + + cnt, err = testEngine.Table("update_table").Update(map[string]interface{}{ + "name": "test2", + "age": 36, + }, &UpdateTable{ + Id: tb.Id, + }) + assert.NoError(t, err) + if testEngine.Dialect().URI().DBType == schemas.SQLITE { + assert.EqualValues(t, 1, cnt) + } else { + assert.EqualValues(t, 0, cnt) + } } func TestUpdateLimit(t *testing.T) { diff --git a/session_update.go b/session_update.go index 7df8c752..0adac25e 100644 --- a/session_update.go +++ b/session_update.go @@ -273,8 +273,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 k = ct.Elem().Kind() } if k == reflect.Struct { + var refTable = session.statement.RefTable + if refTable == nil { + refTable, err = session.engine.TableInfo(condiBean[0]) + if err != nil { + return 0, err + } + } var err error - autoCond, err = session.statement.BuildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) + autoCond, err = session.statement.BuildConds(refTable, condiBean[0], true, true, false, true, false) if err != nil { return 0, err } From 8284e5defa48331f157bff08b1b3c6f53104291d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 5 Jan 2021 11:04:22 +0800 Subject: [PATCH 004/179] Automatically convert datetime to int64 (#1715) Fix #1714 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1715 Reviewed-by: Jerry Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_find_test.go | 23 +++++++++++++++++++++++ session_convert.go | 24 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index c3e99183..0c25a1e9 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -962,3 +962,26 @@ func TestDistinctAndCols(t *testing.T) { assert.EqualValues(t, 1, len(names)) assert.EqualValues(t, "test", names[0]) } + +func TestDateTimeInt64(t *testing.T) { + type DateTimeInt64 struct { + Id int64 + TimeStamp int64 `xorm:"datetime"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(DateTimeInt64)) + + ts := time.Now().Unix() + cnt, err := testEngine.Insert(&DateTimeInt64{ + TimeStamp: ts, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var dts []DateTimeInt64 + err = testEngine.Find(&dts) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(dts)) + assert.EqualValues(t, ts, dts[0].TimeStamp) +} diff --git a/session_convert.go b/session_convert.go index a6839947..4a4dd8be 100644 --- a/session_convert.go +++ b/session_convert.go @@ -168,7 +168,29 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val } else if strings.EqualFold(sdata, "false") { x = 0 } else { - x, err = strconv.ParseInt(sdata, 10, 64) + if col.SQLType.Name == schemas.DateTime { + if len(sdata) == 20 { + t, err := time.Parse("2006-01-02T15:04:05Z", sdata) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + x = t.Unix() + } else if len(sdata) == 19 { + var parseFormat = "2006-01-02 15:04:05" + if sdata[10] == 'T' { + parseFormat = "2006-01-02T15:04:05" + } + t, err := time.Parse(parseFormat, sdata) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + x = t.Unix() + } else { + x, err = strconv.ParseInt(sdata, 10, 64) + } + } else { + x, err = strconv.ParseInt(sdata, 10, 64) + } } if err != nil { return fmt.Errorf("arg %v as int: %s", key, err.Error()) From 91e0b9aa6ec4d5fbb181904539135f23d2f791a1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 5 Jan 2021 11:06:15 +0800 Subject: [PATCH 005/179] Add changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cead87d..32403a1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.0.6](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1308) - 2021-01-05 + +* BUGFIXES + * Fix bug when modify column on mssql (#1849) + * Fix find and count bug with cols (#1826) + * Fix update bug (#1823) + * Fix json tag with other type (#1822) +* ENHANCEMENTS + * prevent panic when struct with unexport field (#1839) + * Automatically convert datetime to int64 (#1715) +* MISC + * Fix index (#1841) + * Performance improvement for columnsbyName (#1788) + ## [1.0.5](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1299) - 2020-09-08 * BUGFIXES From c442c5a9a8f0f81b1a3ef2f23e23e249c99e0c22 Mon Sep 17 00:00:00 2001 From: L-Angel Date: Tue, 19 Jan 2021 13:41:14 +0800 Subject: [PATCH 006/179] fix_bugs_for_mssql (#1852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. add: support xml column type for mssql. 2. fix: array index overflow when getindexs caused by unregular indexname(eg.idx_(tablename)) Co-authored-by: Rick Reviewed-on: https://gitea.com/xorm/xorm/pulls/1852 Reviewed-by: Lunny Xiao Co-authored-by: L-Angel Co-committed-by: L-Angel --- dialects/mssql.go | 2 +- schemas/type.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index 5340455d..4504eb55 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -538,7 +538,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? colName = strings.Trim(colName, "` ") var isRegular bool - if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + if (strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName)) && len(indexName) > (5+len(tableName)) { indexName = indexName[5+len(tableName):] isRegular = true } diff --git a/schemas/type.go b/schemas/type.go index 89459a4d..f0ede296 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -68,6 +68,10 @@ func (s *SQLType) IsJson() bool { return s.Name == Json || s.Name == Jsonb } +func (s *SQLType) IsXML() bool { + return s.Name == XML +} + var ( Bit = "BIT" TinyInt = "TINYINT" @@ -128,6 +132,7 @@ var ( Json = "JSON" Jsonb = "JSONB" + XML = "XML" Array = "ARRAY" SqlTypes = map[string]int{ @@ -144,6 +149,8 @@ var ( Json: TEXT_TYPE, Jsonb: TEXT_TYPE, + XML: TEXT_TYPE, + Char: TEXT_TYPE, NChar: TEXT_TYPE, Varchar: TEXT_TYPE, From 6f4b2fd80e8bd1f9c796cfcfaffcb128202eb3f8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 21 Jan 2021 15:08:48 +0800 Subject: [PATCH 007/179] Fix bug for mssql (#1854) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1854 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/mssql.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dialects/mssql.go b/dialects/mssql.go index 4504eb55..083fb65d 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -220,6 +220,8 @@ type mssql struct { func (db *mssql) Init(uri *URI) error { db.quoter = mssqlQuoter + db.defaultChar = "CHAR" + db.defaultVarchar = "VARCHAR" return db.Base.Init(db, uri) } From 34f68a05eb77fd719aac7bc12fa82256f9abcf0e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 21 Jan 2021 15:10:21 +0800 Subject: [PATCH 008/179] Release v1.0.7 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32403a1a..3ac229c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.0.7](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1336) - 2021-01-21 + +* BUGFIXES + * Fix bug for mssql (#1854) +* MISC + * fix_bugs_for_mssql (#1852) + ## [1.0.6](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1308) - 2021-01-05 * BUGFIXES From 7cd51d58fa4edbcc22720396d5f6f85c70338178 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Jan 2021 10:56:03 +0800 Subject: [PATCH 009/179] Upgrade builder to v0.3.8 (#1855) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1855 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e0d22a24..0fcdb2b8 100644 --- a/go.mod +++ b/go.mod @@ -10,5 +10,5 @@ require ( github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - xorm.io/builder v0.3.7 + xorm.io/builder v0.3.8 ) diff --git a/go.sum b/go.sum index 844dd094..833498ca 100644 --- a/go.sum +++ b/go.sum @@ -66,5 +66,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= -xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE= +xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= From 7c61c09af7cb34bdebf611eae8c80368ab360eaa Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Jan 2021 11:03:43 +0800 Subject: [PATCH 010/179] Revert "Automatically convert datetime to int64 (#1715)" This reverts commit 8284e5defa48331f157bff08b1b3c6f53104291d. --- integrations/session_find_test.go | 23 ----------------------- session_convert.go | 24 +----------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 0c25a1e9..c3e99183 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -962,26 +962,3 @@ func TestDistinctAndCols(t *testing.T) { assert.EqualValues(t, 1, len(names)) assert.EqualValues(t, "test", names[0]) } - -func TestDateTimeInt64(t *testing.T) { - type DateTimeInt64 struct { - Id int64 - TimeStamp int64 `xorm:"datetime"` - } - - assert.NoError(t, PrepareEngine()) - assertSync(t, new(DateTimeInt64)) - - ts := time.Now().Unix() - cnt, err := testEngine.Insert(&DateTimeInt64{ - TimeStamp: ts, - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var dts []DateTimeInt64 - err = testEngine.Find(&dts) - assert.NoError(t, err) - assert.EqualValues(t, 1, len(dts)) - assert.EqualValues(t, ts, dts[0].TimeStamp) -} diff --git a/session_convert.go b/session_convert.go index 4a4dd8be..a6839947 100644 --- a/session_convert.go +++ b/session_convert.go @@ -168,29 +168,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val } else if strings.EqualFold(sdata, "false") { x = 0 } else { - if col.SQLType.Name == schemas.DateTime { - if len(sdata) == 20 { - t, err := time.Parse("2006-01-02T15:04:05Z", sdata) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - x = t.Unix() - } else if len(sdata) == 19 { - var parseFormat = "2006-01-02 15:04:05" - if sdata[10] == 'T' { - parseFormat = "2006-01-02T15:04:05" - } - t, err := time.Parse(parseFormat, sdata) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - x = t.Unix() - } else { - x, err = strconv.ParseInt(sdata, 10, 64) - } - } else { - x, err = strconv.ParseInt(sdata, 10, 64) - } + x, err = strconv.ParseInt(sdata, 10, 64) } if err != nil { return fmt.Errorf("arg %v as int: %s", key, err.Error()) From d41a13b56254b44f60b69dfd75174affc54f1dbb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Jan 2021 11:17:33 +0800 Subject: [PATCH 011/179] Fix tests --- integrations/session_update_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index f2105e2b..df0631c4 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -57,10 +57,10 @@ func TestUpdateMap(t *testing.T) { Id: tb.Id, }) assert.NoError(t, err) - if testEngine.Dialect().URI().DBType == schemas.SQLITE { - assert.EqualValues(t, 1, cnt) - } else { + if testEngine.Dialect().URI().DBType == schemas.MYSQL { assert.EqualValues(t, 0, cnt) + } else { + assert.EqualValues(t, 1, cnt) } } From 4629bb17176a8e0fb14d31c376e22847cf4e50f3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 2 Feb 2021 14:15:10 +0800 Subject: [PATCH 012/179] Add NewEngineWithDB (#1860) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1860 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- engine.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/engine.go b/engine.go index 873fcdc1..f52148aa 100644 --- a/engine.go +++ b/engine.go @@ -105,6 +105,15 @@ func NewEngineWithParams(driverName string, dataSourceName string, params map[st return engine, err } +// NewEngineWithDB new a db manager with db. The params will be passed to db. +func NewEngineWithDB(driverName string, dataSourceName string, db *core.DB) (*Engine, error) { + dialect, err := dialects.OpenDialect(driverName, dataSourceName) + if err != nil { + return nil, err + } + return newEngine(driverName, dataSourceName, dialect, db) +} + // NewEngineWithDialectAndDB new a db manager according to the parameter. // If you do not want to use your own dialect or db, please use NewEngine. // For creating dialect, you can call dialects.OpenDialect. And, for creating db, From ffac8e3577712515307b832fab8f251b57603468 Mon Sep 17 00:00:00 2001 From: whybangbang Date: Wed, 3 Feb 2021 23:27:07 +0800 Subject: [PATCH 013/179] session add method IsInTx (#1863) Co-authored-by: whybangbang <525484036@qq.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1863 Reviewed-by: Lunny Xiao Co-authored-by: whybangbang Co-committed-by: whybangbang --- session_tx.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/session_tx.go b/session_tx.go index 57791703..f50bbf1f 100644 --- a/session_tx.go +++ b/session_tx.go @@ -84,3 +84,8 @@ func (session *Session) Commit() error { } return nil } + +// if current session is in a transaction +func (session *Session) IsInTx() bool { + return !session.isAutoCommit +} \ No newline at end of file From 407da9ddd84acac1554161a4eb83a17663ccb357 Mon Sep 17 00:00:00 2001 From: clannadxr Date: Tue, 9 Feb 2021 15:20:54 +0800 Subject: [PATCH 014/179] export tag identifier (#1865) #1864 Export tag identifier so we can change that form `xorm` to others This will be useful when in some migration scenarios such as migrate `sqlx` to `xorm`, the former uses `db` as the identifier and has a very simple rule which can be covered by xorm parser rules Co-authored-by: clannadxr Reviewed-on: https://gitea.com/xorm/xorm/pulls/1865 Reviewed-by: Lunny Xiao Co-authored-by: clannadxr Co-committed-by: clannadxr --- engine.go | 5 +++++ engine_group.go | 8 ++++++++ interface.go | 1 + tags/parser.go | 5 +++++ tags/parser_test.go | 23 +++++++++++++++++++++++ 5 files changed, 42 insertions(+) diff --git a/engine.go b/engine.go index f52148aa..f5710801 100644 --- a/engine.go +++ b/engine.go @@ -209,6 +209,11 @@ func (engine *Engine) SetColumnMapper(mapper names.Mapper) { engine.tagParser.SetColumnMapper(mapper) } +// SetTagIdentifier set the tag identifier +func (engine *Engine) SetTagIdentifier(tagIdentifier string) { + engine.tagParser.SetIdentifier(tagIdentifier) +} + // Quote Use QuoteStr quote the string sql func (engine *Engine) Quote(value string) string { value = strings.TrimSpace(value) diff --git a/engine_group.go b/engine_group.go index cdd9dd44..3e91cbd6 100644 --- a/engine_group.go +++ b/engine_group.go @@ -167,6 +167,14 @@ func (eg *EngineGroup) SetMapper(mapper names.Mapper) { } } +// SetTagIdentifier set the tag identifier +func (eg *EngineGroup) SetTagIdentifier(tagIdentifier string) { + eg.Engine.SetTagIdentifier(tagIdentifier) + for i := 0; i < len(eg.slaves); i++ { + eg.slaves[i].SetTagIdentifier(tagIdentifier) + } +} + // SetMaxIdleConns set the max idle connections on pool, default is 2 func (eg *EngineGroup) SetMaxIdleConns(conns int) { eg.Engine.DB().SetMaxIdleConns(conns) diff --git a/interface.go b/interface.go index 0fe9cbe1..55162c8c 100644 --- a/interface.go +++ b/interface.go @@ -101,6 +101,7 @@ type EngineInterface interface { SetCacher(string, caches.Cacher) SetConnMaxLifetime(time.Duration) SetColumnMapper(names.Mapper) + SetTagIdentifier(string) SetDefaultCacher(caches.Cacher) SetLogger(logger interface{}) SetLogLevel(log.LogLevel) diff --git a/tags/parser.go b/tags/parser.go index a301d124..45dd6d9d 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -63,6 +63,11 @@ func (parser *Parser) SetColumnMapper(mapper names.Mapper) { parser.columnMapper = mapper } +func (parser *Parser) SetIdentifier(identifier string) { + parser.ClearCaches() + parser.identifier = identifier +} + func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) { t := v.Type() tableI, ok := parser.tableCache.Load(t) diff --git a/tags/parser_test.go b/tags/parser_test.go index ff304a5b..c3bf8051 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -80,3 +80,26 @@ func TestUnexportField(t *testing.T) { assert.NotEqual(t, "public", col.Name) } } + +func TestParseWithOtherIdentifier(t *testing.T) { + parser := NewParser( + "xorm", + dialects.QueryDialect("mysql"), + names.GonicMapper{}, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithDBTag struct { + FieldFoo string `db:"foo"` + } + parser.SetIdentifier("db") + table, err := parser.Parse(reflect.ValueOf(new(StructWithDBTag))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_db_tag", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + + for _, col := range table.Columns() { + assert.EqualValues(t, "foo", col.Name) + } +} From eed7e65bd9fa4614fa0196466f307151ae1b9c86 Mon Sep 17 00:00:00 2001 From: clannadxr Date: Thu, 18 Feb 2021 17:14:13 +0800 Subject: [PATCH 015/179] Panic when setting wrong logger (#1868) Co-authored-by: clannadxr Reviewed-on: https://gitea.com/xorm/xorm/pulls/1868 Reviewed-by: Lunny Xiao Co-authored-by: clannadxr Co-committed-by: clannadxr --- engine.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine.go b/engine.go index f5710801..ee98ed05 100644 --- a/engine.go +++ b/engine.go @@ -168,6 +168,8 @@ func (engine *Engine) SetLogger(logger interface{}) { realLogger = t case log.Logger: realLogger = log.NewLoggerAdapter(t) + default: + panic("logger should implement either log.ContextLogger or log.Logger") } engine.logger = realLogger engine.DB().Logger = realLogger From a7a51533d1dc520b256ec98949ffaf2fe098baeb Mon Sep 17 00:00:00 2001 From: 4color <4color@noreply.gitea.io> Date: Thu, 4 Mar 2021 23:40:35 +0800 Subject: [PATCH 016/179] fix oracle insert datetime (#1873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit oracle下的日期插入问题。 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1873 Reviewed-by: Lunny Xiao Co-authored-by: 4color <4color@noreply.gitea.io> Co-committed-by: 4color <4color@noreply.gitea.io> --- dialects/time.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dialects/time.go b/dialects/time.go index b0394745..9a3c82a4 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -19,7 +19,11 @@ func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{} case schemas.Date: v = t.Format("2006-01-02") case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. - v = t.Format("2006-01-02 15:04:05") + if dialect.URI().DBType == schemas.ORACLE { + v = t + } else { + v = t.Format("2006-01-02 15:04:05") + } case schemas.TimeStampz: if dialect.URI().DBType == schemas.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") From e660414278d25b2fa607b2b9429719626a6e70e1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 15 Mar 2021 23:34:33 +0800 Subject: [PATCH 017/179] Support modernc.org/sqlite (#1850) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1850 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .gitignore | 1 + Makefile | 25 +++++++++-- core/db_test.go | 8 +++- dialects/dialect.go | 1 + go.mod | 3 +- go.sum | 86 +++++++++++++++++++++++++++++-------- integrations/engine_test.go | 1 + integrations/tests.go | 8 +++- 8 files changed, 108 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 617d5da7..a3fbadd4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ test.db.sql *coverage.out test.db integrations/*.sql +integrations/test_sqlite* \ No newline at end of file diff --git a/Makefile b/Makefile index 092f23b3..0f0ddb6c 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,8 @@ help: @echo " - test-mysql run integration tests for mysql" @echo " - test-mssql run integration tests for mssql" @echo " - test-postgres run integration tests for postgres" - @echo " - test-sqlite run integration tests for sqlite" + @echo " - test-sqlite3 run integration tests for sqlite" + @echo " - test-sqlite run integration tests for pure go sqlite" @echo " - test-tidb run integration tests for tidb" @echo " - vet examines Go source code and reports suspicious constructs" @@ -195,21 +196,37 @@ test-postgres\#%: go-check -conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic +.PHONY: test-sqlite3 +test-sqlite3: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-sqlite3-schema +test-sqlite3-schema: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-sqlite3\#% +test-sqlite3\#%: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + .PHONY: test-sqlite test-sqlite: go-check - $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite-schema test-sqlite-schema: go-check - $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite\#% test-sqlite\#%: go-check - $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + .PNONY: test-tidb test-tidb: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ diff --git a/core/db_test.go b/core/db_test.go index 777ab0ad..104c5b95 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -15,6 +15,7 @@ import ( _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" + _ "modernc.org/sqlite" ) var ( @@ -27,7 +28,7 @@ func TestMain(m *testing.M) { flag.Parse() switch *dbtype { - case "sqlite3": + case "sqlite3", "sqlite": createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" case "mysql": @@ -45,8 +46,11 @@ func TestMain(m *testing.M) { func testOpen() (*DB, error) { switch *dbtype { case "sqlite3": - os.Remove("./test.db") + os.Remove("./test_sqlite3.db") return Open("sqlite3", "./test.db") + case "sqlite": + os.Remove("./test_sqlite.db") + return Open("sqlite", "./test.db") case "mysql": return Open("mysql", *dbConn) default: diff --git a/dialects/dialect.go b/dialects/dialect.go index 18b781a1..d9a640a4 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -206,6 +206,7 @@ func regDrvsNDialects() bool { "postgres": {"postgres", func() Driver { return &pqDriver{} }, func() Dialect { return &postgres{} }}, "pgx": {"postgres", func() Driver { return &pqDriverPgx{} }, func() Dialect { return &postgres{} }}, "sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, + "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, } diff --git a/go.mod b/go.mod index 0fcdb2b8..b9eec3a0 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,10 @@ require ( github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc github.com/go-sql-driver/mysql v1.5.0 github.com/lib/pq v1.7.0 - github.com/mattn/go-sqlite3 v1.14.0 + github.com/mattn/go-sqlite3 v1.14.6 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 + modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 xorm.io/builder v0.3.8 ) diff --git a/go.sum b/go.sum index 833498ca..7c72c699 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -16,12 +16,18 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= -github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -29,34 +35,53 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= 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/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -66,5 +91,32 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0= +modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= +modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA= +modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8= +modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY= +modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU= +modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs= +modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= +modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= +modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE= xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 0e5d3424..7f77c21f 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -20,6 +20,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" _ "github.com/ziutek/mymysql/godrv" + _ "modernc.org/sqlite" ) func TestPing(t *testing.T) { diff --git a/integrations/tests.go b/integrations/tests.go index 31fa99bf..840b1020 100644 --- a/integrations/tests.go +++ b/integrations/tests.go @@ -174,7 +174,13 @@ func MainTest(m *testing.M) { dbType = *db if *db == "sqlite3" { if ptrConnStr == nil { - connString = "./test.db?cache=shared&mode=rwc" + connString = "./test_sqlite3.db?cache=shared&mode=rwc" + } else { + connString = *ptrConnStr + } + } else if *db == "sqlite" { + if ptrConnStr == nil { + connString = "./test_sqlite.db?cache=shared&mode=rwc" } else { connString = *ptrConnStr } From 9286f29576542e721d3f7064ec6b943bae995c4d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 23 Mar 2021 21:34:42 +0800 Subject: [PATCH 018/179] Fix bug on import when split SQLs (#1878) Fix #1876 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1878 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/engine_test.go | 9 + integrations/testdata/import1.sql | 279 ++++++++++++++++++++++++++++++ session_schema.go | 36 ++-- 3 files changed, 314 insertions(+), 10 deletions(-) create mode 100644 integrations/testdata/import1.sql diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 7f77c21f..0dfc3de1 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -187,3 +187,12 @@ func TestSetSchema(t *testing.T) { assert.EqualValues(t, oldSchema, testEngine.Dialect().URI().Schema) } } + +func TestImport(t *testing.T) { + sess := testEngine.NewSession() + defer sess.Close() + assert.NoError(t, sess.Begin()) + _, err := sess.ImportFile("./testdata/import1.sql") + assert.NoError(t, err) + assert.NoError(t, sess.Commit()) +} diff --git a/integrations/testdata/import1.sql b/integrations/testdata/import1.sql new file mode 100644 index 00000000..e004f41c --- /dev/null +++ b/integrations/testdata/import1.sql @@ -0,0 +1,279 @@ +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +-- 基本用户信息表 +CREATE TABLE IF NOT EXISTS `user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + KEY `uid` (`id`), + `user_name` varchar(128) CHARACTER SET utf8mb4 NOT NULL, + KEY `user_name` (`user_name`), + `email` varchar(32) NOT NULL, + KEY `email` (`email`), + `pass` varchar(256) NOT NULL, + `passwd` varchar(16) NOT NULL, + `uuid` TEXT NULL DEFAULT NULL COMMENT 'uuid', + `t` int(11) NOT NULL DEFAULT '0', + `u` bigint(20) NOT NULL, + `d` bigint(20) NOT NULL, + `plan` varchar(2) CHARACTER SET utf8mb4 NOT NULL DEFAULT 'A', + `node_group` INT NOT NULL DEFAULT '0', + `auto_reset_day` INT NOT NULL DEFAULT '0', + `auto_reset_bandwidth` DECIMAL(12,2) NOT NULL DEFAULT '0.00', + `transfer_enable` BIGINT(20) NOT NULL, + `port` int(11) NOT NULL, + `protocol_param` VARCHAR(128) NULL DEFAULT NULL, + `obfs_param` VARCHAR(128) NULL DEFAULT NULL, + `switch` tinyint(4) NOT NULL DEFAULT '1', + `enable` tinyint(4) NOT NULL DEFAULT '1', + `type` tinyint(4) NOT NULL DEFAULT '1', + `last_get_gift_time` int(11) NOT NULL DEFAULT '0', + `last_check_in_time` int(11) NOT NULL DEFAULT '0', + `last_rest_pass_time` int(11) NOT NULL DEFAULT '0', + `reg_date` datetime NOT NULL, + `invite_num` int(8) NOT NULL, + `money` decimal(12,2) NOT NULL, + `ref_by` int(11) NOT NULL DEFAULT '0', + `expire_time` int(11) NOT NULL DEFAULT '0', + `is_email_verify` tinyint(4) NOT NULL DEFAULT '0', + `reg_ip` varchar(128) NOT NULL DEFAULT '127.0.0.1', + `node_speedlimit` DECIMAL(12,2) NOT NULL DEFAULT '0.00', + `node_connector` int(11) NOT NULL DEFAULT '0', + `forbidden_ip` LONGTEXT NULL DEFAULT '', + `forbidden_port` LONGTEXT NULL DEFAULT '', + `disconnect_ip` LONGTEXT NULL DEFAULT '', + `is_hide` INT NOT NULL DEFAULT '0', + `last_detect_ban_time` datetime DEFAULT '1989-06-04 00:05:00', + `all_detect_number` int(11) NOT NULL DEFAULT '0', + `is_multi_user` INT NOT NULL DEFAULT '0', + `telegram_id` BIGINT NULL, + `is_admin` int(2) NOT NULL DEFAULT '0', + `im_type` int(11) DEFAULT '1', + `im_value` text, + `last_day_t` bigint(20) NOT NULL DEFAULT '0', + `mail_notified` int(11) NOT NULL DEFAULT '0', + `class` int(11) NOT NULL DEFAULT '0', + `class_expire` datetime NOT NULL DEFAULT '1989-06-04 00:05:00', + `expire_in` datetime NOT NULL DEFAULT '2099-06-04 00:05:00', + `theme` text NOT NULL, + `ga_token` text NOT NULL, + `ga_enable` int(11) NOT NULL DEFAULT '0', + `pac` LONGTEXT, + `remark` text +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 用户流量信息表 +-- TODO: 重写流量信息提取逻辑 +CREATE TABLE IF NOT EXISTS `user_traffic_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `user_id` int(11) NOT NULL, + `u` BIGINT(20) NOT NULL, + `d` BIGINT(20) NOT NULL, + `node_id` int(11) NOT NULL, + `rate` float NOT NULL, + `traffic` varchar(32) NOT NULL, + `log_time` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 用户订阅 TOKEN 信息表 +CREATE TABLE IF NOT EXISTS `user_token` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `token` varchar(256) NOT NULL, + `user_id` int(11) NOT NULL, + `create_time` int(11) NOT NULL, + `expire_time` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 充值码使用信息表 +CREATE TABLE IF NOT EXISTS `charge_code` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `code` text NOT NULL, + `type` int(11) NOT NULL, + `number` DECIMAL(11,2) NOT NULL, + `isused` int(11) NOT NULL DEFAULT '0', + `userid` bigint(20) NOT NULL, + `usedatetime` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 邀请码使用信息表 +CREATE TABLE IF NOT EXISTS `invite_code` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `code` varchar(128) NOT NULL, + KEY `user_id` (`user_id`), + `user_id` int(11) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT '2016-06-01 00:00:00' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 公告信息表 +CREATE TABLE IF NOT EXISTS `announcement` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `date` datetime NOT NULL, + `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `markdown` LONGTEXT NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 节点信息表 +CREATE TABLE IF NOT EXISTS `node` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `name` varchar(128) NOT NULL, + `type` int(3) NOT NULL, + `online_user` int(11) NOT NULL, + `mu_only` INT NULL DEFAULT '0', + `online` BOOLEAN NOT NULL DEFAULT TRUE, + `server` varchar(128) NOT NULL, + `method` varchar(64) NOT NULL, + `info` varchar(128) NOT NULL, + `status` varchar(128) NOT NULL, + `node_group` INT NOT NULL DEFAULT '0', + `sort` int(3) NOT NULL, + `custom_method` tinyint(1) NOT NULL DEFAULT '0', + `traffic_rate` float NOT NULL DEFAULT '1', + `node_class` int(11) NOT NULL DEFAULT '0', + `node_speedlimit` DECIMAL(12,2) NOT NULL DEFAULT '0.00', + `node_connector` int(11) NOT NULL DEFAULT '0', + `node_bandwidth` bigint(20) NOT NULL DEFAULT '0', + `node_bandwidth_limit` bigint(20) NOT NULL DEFAULT '0', + `bandwidthlimit_resetday` int(11) NOT NULL DEFAULT '0', + `node_heartbeat` bigint(20) NOT NULL DEFAULT '0', + `node_ip` text +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +-- TODO: 修改 VPN 节点的结算说明 + +-- 商店数据表 +CREATE TABLE `shop` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `name` TEXT NOT NULL, + `price` DECIMAL(12,2) NOT NULL, + `content` TEXT NOT NULL, + `auto_renew` INT NOT NULL, + `status` INT NOT NULL DEFAULT '1', + `auto_reset_bandwidth` INT NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 优惠券数据表 +CREATE TABLE `coupon` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `code` TEXT NOT NULL, + `onetime` INT NOT NULL, + `expire` BIGINT NOT NULL, + `shop` TEXT NOT NULL, + `credit` INT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 购买记录数据表 +CREATE TABLE `bought` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `userid` BIGINT NOT NULL, + `shopid` BIGINT NOT NULL, + `coupon` TEXT NOT NULL, + `datetime` BIGINT NOT NULL, + `renew` BIGINT(11) NOT NULL, + `price` DECIMAL(12,2) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 工单数据表 +CREATE TABLE `ticket` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `title` LONGTEXT NOT NULL, + `status` INT NOT NULL DEFAULT '1', + `content` LONGTEXT NOT NULL, + `rootid` BIGINT NOT NULL,`userid` BIGINT NOT NULL, + `datetime` BIGINT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 返利记录数据表 +CREATE TABLE `payback` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `total` DECIMAL(12,2) NOT NULL, + `userid` BIGINT NOT NULL, + `ref_by` BIGINT NOT NULL, + `ref_get` DECIMAL(12,2) NOT NULL, + `datetime` BIGINT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 审计规则数据表 +CREATE TABLE `detect_list` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `name` LONGTEXT NOT NULL, + `type` INT NOT NULL, + `text` LONGTEXT NOT NULL, + `regex` LONGTEXT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 审计记录数据表 +CREATE TABLE `detect_log` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `node_id` INT NOT NULL, + `list_id` BIGINT NOT NULL, + `datetime` BIGINT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 中转规则数据表 +CREATE TABLE IF NOT EXISTS `relay` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + `user_id` bigint(20) NOT NULL, + `source_node_id` bigint(20) NOT NULL, + `dist_node_id` bigint(20) NOT NULL, + `dist_ip` text NOT NULL, + `port` int(11) NOT NULL, + `priority` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 用户订阅日志 +CREATE TABLE IF NOT EXISTS `user_subscribe_log` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `user_name` varchar(128) NOT NULL COMMENT '用户名', + `user_id` int(11) NOT NULL COMMENT '用户 ID', + `email` varchar(32) NOT NULL COMMENT '用户邮箱', + `subscribe_type` varchar(20) NOT NULL COMMENT '获取的订阅类型', + `request_ip` varchar(128) NOT NULL COMMENT '请求 IP', + `request_time` datetime NOT NULL COMMENT '请求时间', + `request_user_agent` text COMMENT '请求 UA 信息', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户订阅日志'; + +-- 审计封禁日志 +CREATE TABLE IF NOT EXISTS `detect_ban_log` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `user_name` varchar(128) NOT NULL COMMENT '用户名', + `user_id` int(11) NOT NULL COMMENT '用户 ID', + `email` varchar(32) NOT NULL COMMENT '用户邮箱', + `detect_number` int(11) NOT NULL COMMENT '本次违规次数', + `ban_time` int(11) NOT NULL COMMENT '本次封禁时长', + `start_time` bigint(20) NOT NULL COMMENT '统计开始时间', + `end_time` bigint(20) NOT NULL COMMENT '统计结束时间', + `all_detect_number` int(11) NOT NULL COMMENT '累计违规次数', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审计封禁日志'; + +-- 管理员操作记录 +CREATE TABLE IF NOT EXISTS `gconfig` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(128) NOT NULL COMMENT '配置键名', + `type` varchar(32) NOT NULL COMMENT '值类型', + `value` text NOT NULL COMMENT '配置值', + `oldvalue` text NOT NULL COMMENT '之前的配置值', + `name` varchar(128) NOT NULL COMMENT '配置名称', + `comment` text NOT NULL COMMENT '配置描述', + `operator_id` int(11) NOT NULL COMMENT '操作员 ID', + `operator_name` varchar(128) NOT NULL COMMENT '操作员名称', + `operator_email` varchar(32) NOT NULL COMMENT '操作员邮箱', + `last_update` bigint(20) NOT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='网站配置'; \ No newline at end of file diff --git a/session_schema.go b/session_schema.go index 9ccf8abe..7d36ae7f 100644 --- a/session_schema.go +++ b/session_schema.go @@ -448,27 +448,43 @@ func (session *Session) ImportFile(ddlPath string) ([]sql.Result, error) { // Import SQL DDL from io.Reader func (session *Session) Import(r io.Reader) ([]sql.Result, error) { - var results []sql.Result - var lastError error - scanner := bufio.NewScanner(r) + var ( + results []sql.Result + lastError error + inSingleQuote bool + startComment bool + ) - var inSingleQuote bool + scanner := bufio.NewScanner(r) semiColSpliter := func(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } + var oriInSingleQuote = inSingleQuote for i, b := range data { - if b == '\'' { - inSingleQuote = !inSingleQuote - } - if !inSingleQuote && b == ';' { - return i + 1, data[0:i], nil + if startComment { + if b == '\n' { + startComment = false + } + } else { + if i > 0 && data[i-1] == '-' && data[i] == '-' { + startComment = true + continue + } + + if b == '\'' { + inSingleQuote = !inSingleQuote + } + if !inSingleQuote && b == ';' { + return i + 1, data[0:i], nil + } } } // If we're at EOF, we have a final, non-terminated line. Return it. if atEOF { return len(data), data, nil } + inSingleQuote = oriInSingleQuote // Request more data. return 0, nil, nil } @@ -479,10 +495,10 @@ func (session *Session) Import(r io.Reader) ([]sql.Result, error) { query := strings.Trim(scanner.Text(), " \t\n\r") if len(query) > 0 { result, err := session.Exec(query) - results = append(results, result) if err != nil { return nil, err } + results = append(results, result) } } From 40ee326cac59ffba1234d48dfbb62ea633c4d3c6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 23 Mar 2021 21:48:58 +0800 Subject: [PATCH 019/179] More clear for Get with nil (#1879) Resolve #1844 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1879 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_get_test.go | 16 ++++++++++++++++ session_get.go | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index e4d9f82e..99db98fc 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -6,11 +6,13 @@ package integrations import ( "database/sql" + "errors" "fmt" "strconv" "testing" "time" + "xorm.io/xorm" "xorm.io/xorm/contexts" "xorm.io/xorm/schemas" @@ -750,3 +752,17 @@ func TestGetViaMapCond(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func TestGetNil(t *testing.T) { + type GetNil struct { + Id int64 + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetNil)) + + var gn *GetNil + has, err := testEngine.Get(gn) + assert.True(t, errors.Is(err, xorm.ErrObjectIsNil)) + assert.False(t, has) +} diff --git a/session_get.go b/session_get.go index afedcd1f..6e65ea2f 100644 --- a/session_get.go +++ b/session_get.go @@ -16,6 +16,10 @@ import ( "xorm.io/xorm/schemas" ) +var ( + ErrObjectIsNil = errors.New("object should not be nil") +) + // Get retrieve one record from database, bean's non-empty fields // will be as conditions func (session *Session) Get(bean interface{}) (bool, error) { @@ -37,6 +41,8 @@ func (session *Session) get(bean interface{}) (bool, error) { return false, errors.New("needs a pointer to a value") } else if beanValue.Elem().Kind() == reflect.Ptr { return false, errors.New("a pointer to a pointer is not allowed") + } else if beanValue.IsNil() { + return false, ErrObjectIsNil } if beanValue.Elem().Kind() == reflect.Struct { From 21881d8b84ba38e22f26dc82a60360d444df29da Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 8 Apr 2021 14:12:40 +0800 Subject: [PATCH 020/179] MariaDB 10.5 adds a suffix on old datatypes (#1885) MariaDB when encountering an old datetime type will add a suffix of /* mariadb-5.3 */ on its schema information page. Xorm does not understand this and then its mappings fail. The simplest solution is just to remove any fixed suffix and that is what this PR does - a clever solution would look for and remove any comments or match them. See https://mariadb.com/kb/en/time/ for more details about the comment. Related: https://github.com/go-gitea/gitea/issues/15277 Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1885 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/mysql.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dialects/mysql.go b/dialects/mysql.go index 32e18a17..769e30b1 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -353,6 +353,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cts := strings.Split(colType, "(") colName := cts[0] + // Remove the /* mariadb-5.3 */ suffix from coltypes + colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */") colType = strings.ToUpper(colName) var len1, len2 int if len(cts) == 2 { From 2142e31d95557ca9db2201e440938f8f4538224d Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 8 Apr 2021 20:51:48 +0800 Subject: [PATCH 021/179] Fix formatting (#1886) Fix newline formatting Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1886 Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- session_tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session_tx.go b/session_tx.go index f50bbf1f..33ef72c6 100644 --- a/session_tx.go +++ b/session_tx.go @@ -88,4 +88,4 @@ func (session *Session) Commit() error { // if current session is in a transaction func (session *Session) IsInTx() bool { return !session.isAutoCommit -} \ No newline at end of file +} From ee78664413c09868df2867d8b0f60cdb4ec60658 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 8 Apr 2021 21:26:21 +0800 Subject: [PATCH 022/179] Fix sqlite test (#1887) Fix bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1887 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/engine_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 0dfc3de1..1e2e9f4a 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -189,6 +189,10 @@ func TestSetSchema(t *testing.T) { } func TestImport(t *testing.T) { + if testEngine.Dialect().URI().DBType != schemas.MYSQL { + t.SkipNow() + return + } sess := testEngine.NewSession() defer sess.Close() assert.NoError(t, sess.Begin()) From b92d951eac56ed56db9991dac819960f51221561 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 10 Apr 2021 09:45:18 +0800 Subject: [PATCH 023/179] Fix drone (#1842) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1842 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 790 ++++++++++++++++++++++++++++++++++++++++------------- Makefile | 4 +- 2 files changed, 599 insertions(+), 195 deletions(-) diff --git a/.drone.yml b/.drone.yml index 300c7841..05f06e99 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,58 +2,288 @@ kind: pipeline name: testing steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + - name: test-vet - image: golang:1.11 # The lowest golang requirement + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' commands: - make vet - - make test - make fmt-check + volumes: + - name: cache + path: /go when: event: - push - pull_request -- name: test-sqlite - image: golang:1.12 +- name: rebuild-cache + image: meltwater/drone-cache + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +--- +kind: pipeline +name: test-sqlite +depends_on: + - testing +steps: +- name: restore-cache + image: meltwater/drone-cache:dev + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +- name: test-sqlite3 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + commands: + - make test-sqlite3 + - TEST_CACHE_ENABLE=true make test-sqlite3 + - TEST_QUOTE_POLICY=reserved make test-sqlite3 + volumes: + - name: cache + path: /go + +- name: test-sqlite + image: golang:1.15 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' commands: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite - TEST_QUOTE_POLICY=reserved make test-sqlite - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +--- +kind: pipeline +name: test-mysql +depends_on: + - testing +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-mysql - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql TEST_MYSQL_CHARSET: utf8 TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root TEST_MYSQL_PASSWORD: commands: + - make test + - make test-mysql + - TEST_CACHE_ENABLE=true make test-mysql + - TEST_QUOTE_POLICY=reserved make test-mysql + volumes: + - name: cache + path: /go + +- name: test-mysql-utf8mb4 + image: golang:1.15 + depends_on: + - test-mysql + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + TEST_MYSQL_HOST: mysql + TEST_MYSQL_CHARSET: utf8mb4 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + commands: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: test-mysql8 - image: golang:1.12 +- name: test-mymysql + pull: default + image: golang:1.15 + depends_on: + - test-mysql-utf8mb4 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + TEST_MYSQL_HOST: mysql:3306 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + commands: + - make test-mymysql + - TEST_CACHE_ENABLE=true make test-mymysql + - TEST_QUOTE_POLICY=reserved make test-mymysql + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache + depends_on: + - test-mysql + - test-mysql-utf8mb4 + - test-mymysql + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mysql + pull: default + image: mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + +--- +kind: pipeline +name: test-mysql8 +depends_on: + - test-mysql + - test-sqlite +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +- name: test-mysql8 + image: golang:1.15 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql8 TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test @@ -63,58 +293,70 @@ steps: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: test-mysql-utf8mb4 - image: golang:1.12 +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true depends_on: - - test-mysql - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MYSQL_HOST: mysql - TEST_MYSQL_CHARSET: utf8mb4 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mysql - - TEST_CACHE_ENABLE=true make test-mysql - - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + - test-mysql8 + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go -- name: test-mymysql +volumes: + - name: cache + temp: {} + +services: +- name: mysql8 pull: default - image: golang:1.12 - depends_on: - - test-mysql-utf8mb4 + image: mysql:8.0 environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MYSQL_HOST: mysql:3306 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mymysql - - TEST_CACHE_ENABLE=true make test-mymysql - - TEST_QUOTE_POLICY=reserved make test-mymysql - when: - event: - - push - - pull_request + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + +--- +kind: pipeline +name: test-mariadb +depends_on: + - test-mysql8 +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-mariadb - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mariadb TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test @@ -124,17 +366,71 @@ steps: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + depends_on: + - test-mariadb + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mariadb + pull: default + image: mariadb:10.4 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + +--- +kind: pipeline +name: test-postgres +depends_on: + - test-mariadb +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-postgres pull: default - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_PGSQL_HOST: pgsql TEST_PGSQL_DBNAME: xorm_test TEST_PGSQL_USERNAME: postgres @@ -143,19 +439,21 @@ steps: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres - TEST_QUOTE_POLICY=reserved make test-postgres - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go - name: test-postgres-schema pull: default - image: golang:1.12 + image: golang:1.15 depends_on: - test-postgres environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_PGSQL_HOST: pgsql TEST_PGSQL_SCHEMA: xorm TEST_PGSQL_DBNAME: xorm_test @@ -165,17 +463,72 @@ steps: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres - TEST_QUOTE_POLICY=reserved make test-postgres - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + depends_on: + - test-postgres-schema + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: pgsql + pull: default + image: postgres:9.5 + environment: + POSTGRES_DB: xorm_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + +--- +kind: pipeline +name: test-mssql +depends_on: + - test-postgres +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-mssql pull: default - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MSSQL_HOST: mssql TEST_MSSQL_DBNAME: xorm_test TEST_MSSQL_USERNAME: sa @@ -185,17 +538,70 @@ steps: - TEST_CACHE_ENABLE=true make test-mssql - TEST_QUOTE_POLICY=reserved make test-mssql - TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mssql + pull: default + image: microsoft/mssql-server-linux:latest + environment: + ACCEPT_EULA: Y + SA_PASSWORD: yourStrong(!)Password + MSSQL_PID: Developer + +--- +kind: pipeline +name: test-tidb +depends_on: + - test-mssql +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-tidb pull: default - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_TIDB_HOST: "tidb:4000" TEST_TIDB_DBNAME: xorm_test TEST_TIDB_USERNAME: root @@ -204,17 +610,66 @@ steps: - make test-tidb - TEST_CACHE_ENABLE=true make test-tidb - TEST_QUOTE_POLICY=reserved make test-tidb - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: tidb + pull: default + image: pingcap/tidb:v3.0.3 + +--- +kind: pipeline +name: test-cockroach +depends_on: + - test-tidb +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-cockroach pull: default - image: golang:1.13 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_COCKROACH_HOST: "cockroach:26257" TEST_COCKROACH_DBNAME: xorm_test TEST_COCKROACH_USERNAME: root @@ -223,115 +678,62 @@ steps: - sleep 10 - make test-cockroach - TEST_CACHE_ENABLE=true make test-cockroach - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: merge_coverage - pull: default - image: golang:1.12 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - depends_on: - - test-vet - - test-sqlite - - test-mysql - - test-mysql8 - - test-mymysql - - test-postgres - - test-postgres-schema - - test-mssql - - test-tidb - - test-cockroach - commands: - - make coverage - when: - event: - - push - - pull_request +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} services: - -- name: mysql - pull: default - image: mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - when: - event: - - push - - tag - - pull_request - -- name: mysql8 - pull: default - image: mysql:8.0 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - when: - event: - - push - - tag - - pull_request - -- name: mariadb - pull: default - image: mariadb:10.4 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - when: - event: - - push - - tag - - pull_request - -- name: pgsql - pull: default - image: postgres:9.5 - environment: - POSTGRES_DB: xorm_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - when: - event: - - push - - tag - - pull_request - -- name: mssql - pull: default - image: microsoft/mssql-server-linux:latest - environment: - ACCEPT_EULA: Y - SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Developer - when: - event: - - push - - tag - - pull_request - -- name: tidb - pull: default - image: pingcap/tidb:v3.0.3 - when: - event: - - push - - tag - - pull_request - - name: cockroach pull: default image: cockroachdb/cockroach:v19.2.4 commands: - /cockroach/cockroach start --insecure + +--- +kind: pipeline +name: merge_coverage +depends_on: + - testing + - test-sqlite + - test-mysql + - test-mysql8 + - test-mariadb + - test-postgres + - test-mssql + - test-tidb + - test-cockroach +steps: +- name: merge_coverage + pull: default + image: golang:1.15 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + commands: + - make coverage when: + branch: + - master event: - - push - - tag - - pull_request + - push + - pull_request diff --git a/Makefile b/Makefile index 0f0ddb6c..7305baa7 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,9 @@ GOFMT ?= gofmt -s TAGS ?= SED_INPLACE := sed -i -GOFILES := $(shell find . -name "*.go" -type f) +GO_DIRS := caches contexts integrations convert core dialects internal log migrate names schemas tags +GOFILES := $(wildcard *.go) +GOFILES += $(shell find $(GO_DIRS) -name "*.go" -type f) INTEGRATION_PACKAGES := xorm.io/xorm/integrations PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...)) From 4bfe6853f595740a9bdeb7053c87079495ce96b8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 10 Apr 2021 10:57:36 +0800 Subject: [PATCH 024/179] Fix some comments lint and bug (#1888) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1888 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- caches/leveldb.go | 5 +++++ caches/manager.go | 4 ++++ contexts/hook.go | 6 ++++++ dialects/dialect.go | 35 ++++++++++++++++++++------------ engine.go | 3 ++- engine_group.go | 3 ++- integrations/engine_test.go | 2 +- integrations/tests.go | 10 ++++++++++ internal/json/json.go | 6 +++--- internal/utils/name.go | 1 + internal/utils/reflect.go | 1 + internal/utils/sql.go | 1 + internal/utils/strings.go | 3 +++ internal/utils/zero.go | 40 +++++++++++++++++++++---------------- migrate/migrate.go | 5 +---- migrate/migrate_test.go | 5 +---- session.go | 3 ++- session_get.go | 1 + session_tx.go | 2 +- 19 files changed, 90 insertions(+), 46 deletions(-) diff --git a/caches/leveldb.go b/caches/leveldb.go index d1a177ad..f2f71d84 100644 --- a/caches/leveldb.go +++ b/caches/leveldb.go @@ -19,6 +19,7 @@ type LevelDBStore struct { var _ CacheStore = &LevelDBStore{} +// NewLevelDBStore creates a leveldb store func NewLevelDBStore(dbfile string) (*LevelDBStore, error) { db := &LevelDBStore{} h, err := leveldb.OpenFile(dbfile, nil) @@ -29,6 +30,7 @@ func NewLevelDBStore(dbfile string) (*LevelDBStore, error) { return db, nil } +// Put implements CacheStore func (s *LevelDBStore) Put(key string, value interface{}) error { val, err := Encode(value) if err != nil { @@ -50,6 +52,7 @@ func (s *LevelDBStore) Put(key string, value interface{}) error { return err } +// Get implements CacheStore func (s *LevelDBStore) Get(key string) (interface{}, error) { data, err := s.store.Get([]byte(key), nil) if err != nil { @@ -75,6 +78,7 @@ func (s *LevelDBStore) Get(key string) (interface{}, error) { return s.v, err } +// Del implements CacheStore func (s *LevelDBStore) Del(key string) error { err := s.store.Delete([]byte(key), nil) if err != nil { @@ -89,6 +93,7 @@ func (s *LevelDBStore) Del(key string) error { return err } +// Close implements CacheStore func (s *LevelDBStore) Close() { s.store.Close() } diff --git a/caches/manager.go b/caches/manager.go index 05045210..89a14106 100644 --- a/caches/manager.go +++ b/caches/manager.go @@ -6,6 +6,7 @@ package caches import "sync" +// Manager represents a cache manager type Manager struct { cacher Cacher disableGlobalCache bool @@ -14,6 +15,7 @@ type Manager struct { cacherLock sync.RWMutex } +// NewManager creates a cache manager func NewManager() *Manager { return &Manager{ cachers: make(map[string]Cacher), @@ -27,12 +29,14 @@ func (mgr *Manager) SetDisableGlobalCache(disable bool) { } } +// SetCacher set cacher of table func (mgr *Manager) SetCacher(tableName string, cacher Cacher) { mgr.cacherLock.Lock() mgr.cachers[tableName] = cacher mgr.cacherLock.Unlock() } +// GetCacher returns a cache of a table func (mgr *Manager) GetCacher(tableName string) Cacher { var cacher Cacher var ok bool diff --git a/contexts/hook.go b/contexts/hook.go index 71ad8e87..70f114dd 100644 --- a/contexts/hook.go +++ b/contexts/hook.go @@ -31,6 +31,7 @@ func NewContextHook(ctx context.Context, sql string, args []interface{}) *Contex } } +// End finish the hook invokation func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) { c.Ctx = ctx c.Result = result @@ -38,19 +39,23 @@ func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) { c.ExecuteTime = time.Now().Sub(c.start) } +// Hook represents a hook behaviour type Hook interface { BeforeProcess(c *ContextHook) (context.Context, error) AfterProcess(c *ContextHook) error } +// Hooks implements Hook interface but contains multiple Hook type Hooks struct { hooks []Hook } +// AddHook adds a Hook func (h *Hooks) AddHook(hooks ...Hook) { h.hooks = append(h.hooks, hooks...) } +// BeforeProcess invoked before execute the process func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) { ctx := c.Ctx for _, h := range h.hooks { @@ -63,6 +68,7 @@ func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) { return ctx, nil } +// AfterProcess invoked after exetue the process func (h *Hooks) AfterProcess(c *ContextHook) error { firstErr := c.Err for _, h := range h.hooks { diff --git a/dialects/dialect.go b/dialects/dialect.go index d9a640a4..b02ec4ae 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -79,32 +79,34 @@ type Base struct { quoter schemas.Quoter } -func (b *Base) Quoter() schemas.Quoter { - return b.quoter +// Quoter returns the current database Quoter +func (db *Base) Quoter() schemas.Quoter { + return db.quoter } -func (b *Base) Init(dialect Dialect, uri *URI) error { - b.dialect, b.uri = dialect, uri +// Init initialize the dialect +func (db *Base) Init(dialect Dialect, uri *URI) error { + db.dialect, db.uri = dialect, uri return nil } -func (b *Base) URI() *URI { - return b.uri +// URI returns the uri of database +func (db *Base) URI() *URI { + return db.uri } -func (b *Base) DBType() schemas.DBType { - return b.uri.DBType -} - -func (b *Base) FormatBytes(bs []byte) string { +// FormatBytes formats bytes +func (db *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } +// DropTableSQL returns drop table SQL func (db *Base) DropTableSQL(tableName string) (string, bool) { quote := db.dialect.Quoter().Quote return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)), true } +// HasRecords returns true if the SQL has records returned func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query string, args ...interface{}) (bool, error) { rows, err := queryer.QueryContext(ctx, query, args...) if err != nil { @@ -118,6 +120,7 @@ func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query stri return false, nil } +// IsColumnExist returns true if the column of the table exist func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { quote := db.dialect.Quoter().Quote query := fmt.Sprintf( @@ -132,11 +135,13 @@ func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableNa return db.HasRecords(queryer, ctx, query, db.uri.DBName, tableName, colName) } +// AddColumnSQL returns a SQL to add a column func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, true) return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), s) } +// CreateIndexSQL returns a SQL to create index func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { quoter := db.dialect.Quoter() var unique string @@ -150,6 +155,7 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { quoter.Join(index.Cols, ",")) } +// DropIndexSQL returns a SQL to drop index func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { quote := db.dialect.Quoter().Quote var name string @@ -161,16 +167,19 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName)) } +// ModifyColumnSQL returns a SQL to modify SQL func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, false) return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s) } -func (b *Base) ForUpdateSQL(query string) string { +// ForUpdateSQL returns for updateSQL +func (db *Base) ForUpdateSQL(query string) string { return query + " FOR UPDATE" } -func (b *Base) SetParams(params map[string]string) { +// SetParams set params +func (db *Base) SetParams(params map[string]string) { } var ( diff --git a/engine.go b/engine.go index ee98ed05..384928d6 100644 --- a/engine.go +++ b/engine.go @@ -1278,6 +1278,7 @@ func (engine *Engine) SetSchema(schema string) { engine.dialect.URI().SetSchema(schema) } +// AddHook adds a context Hook func (engine *Engine) AddHook(hook contexts.Hook) { engine.db.AddHook(hook) } @@ -1293,7 +1294,7 @@ func (engine *Engine) tbNameWithSchema(v string) string { return dialects.TableNameWithSchema(engine.dialect, v) } -// ContextHook creates a session with the context +// Context creates a session with the context func (engine *Engine) Context(ctx context.Context) *Session { session := engine.NewSession() session.isAutoClose = true diff --git a/engine_group.go b/engine_group.go index 3e91cbd6..3569690b 100644 --- a/engine_group.go +++ b/engine_group.go @@ -79,7 +79,7 @@ func (eg *EngineGroup) Close() error { return nil } -// ContextHook returned a group session +// Context returned a group session func (eg *EngineGroup) Context(ctx context.Context) *Session { sess := eg.NewSession() sess.isAutoClose = true @@ -144,6 +144,7 @@ func (eg *EngineGroup) SetLogger(logger interface{}) { } } +// AddHook adds Hook func (eg *EngineGroup) AddHook(hook contexts.Hook) { eg.Engine.AddHook(hook) for i := 0; i < len(eg.slaves); i++ { diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 1e2e9f4a..3b843f16 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -190,7 +190,7 @@ func TestSetSchema(t *testing.T) { func TestImport(t *testing.T) { if testEngine.Dialect().URI().DBType != schemas.MYSQL { - t.SkipNow() + t.Skip() return } sess := testEngine.NewSession() diff --git a/integrations/tests.go b/integrations/tests.go index 840b1020..512f3962 100644 --- a/integrations/tests.go +++ b/integrations/tests.go @@ -8,6 +8,7 @@ import ( "database/sql" "flag" "fmt" + "net/url" "os" "strings" "testing" @@ -97,6 +98,13 @@ func createEngine(dbType, connStr string) error { return fmt.Errorf("db.Exec: %v", err) } db.Close() + case schemas.SQLITE, "sqlite": + u, err := url.Parse(connStr) + if err != nil { + return err + } + connStr = u.Path + *ignoreSelectUpdate = true default: *ignoreSelectUpdate = true } @@ -164,10 +172,12 @@ func createEngine(dbType, connStr string) error { return nil } +// PrepareEngine prepare tests ORM engine func PrepareEngine() error { return createEngine(dbType, connString) } +// MainTest the tests entrance func MainTest(m *testing.M) { flag.Parse() diff --git a/internal/json/json.go b/internal/json/json.go index c9a2eb4e..ef52f51f 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -6,15 +6,15 @@ package json import "encoding/json" -// JSONInterface represents an interface to handle json data -type JSONInterface interface { +// Interface represents an interface to handle json data +type Interface interface { Marshal(v interface{}) ([]byte, error) Unmarshal(data []byte, v interface{}) error } var ( // DefaultJSONHandler default json handler - DefaultJSONHandler JSONInterface = StdJSON{} + DefaultJSONHandler Interface = StdJSON{} ) // StdJSON implements JSONInterface via encoding/json diff --git a/internal/utils/name.go b/internal/utils/name.go index f5fc3ff7..840dd9e8 100644 --- a/internal/utils/name.go +++ b/internal/utils/name.go @@ -8,6 +8,7 @@ import ( "fmt" ) +// IndexName returns index name func IndexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } diff --git a/internal/utils/reflect.go b/internal/utils/reflect.go index 3dad6bfe..7973d4d3 100644 --- a/internal/utils/reflect.go +++ b/internal/utils/reflect.go @@ -8,6 +8,7 @@ import ( "reflect" ) +// ReflectValue returns value of a bean func ReflectValue(bean interface{}) reflect.Value { return reflect.Indirect(reflect.ValueOf(bean)) } diff --git a/internal/utils/sql.go b/internal/utils/sql.go index 5e68c4a4..369ca2b8 100644 --- a/internal/utils/sql.go +++ b/internal/utils/sql.go @@ -8,6 +8,7 @@ import ( "strings" ) +// IsSubQuery returns true if it contains a sub query func IsSubQuery(tbName string) bool { const selStr = "select" if len(tbName) <= len(selStr)+1 { diff --git a/internal/utils/strings.go b/internal/utils/strings.go index 72466705..86469c0f 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -8,10 +8,12 @@ import ( "strings" ) +// IndexNoCase index a string in a string with no care of capitalize func IndexNoCase(s, sep string) int { return strings.Index(strings.ToLower(s), strings.ToLower(sep)) } +// SplitNoCase split a string by a seperator with no care of capitalize func SplitNoCase(s, sep string) []string { idx := IndexNoCase(s, sep) if idx < 0 { @@ -20,6 +22,7 @@ func SplitNoCase(s, sep string) []string { return strings.Split(s, s[idx:idx+len(sep)]) } +// SplitNNoCase split n by a seperator with no care of capitalize func SplitNNoCase(s, sep string, n int) []string { idx := IndexNoCase(s, sep) if idx < 0 { diff --git a/internal/utils/zero.go b/internal/utils/zero.go index 8f033c60..007e3c33 100644 --- a/internal/utils/zero.go +++ b/internal/utils/zero.go @@ -9,6 +9,7 @@ import ( "time" ) +// Zeroable represents an interface which could know if it's a zero value type Zeroable interface { IsZero() bool } @@ -21,39 +22,39 @@ func IsZero(k interface{}) bool { return true } - switch k.(type) { + switch t := k.(type) { case int: - return k.(int) == 0 + return t == 0 case int8: - return k.(int8) == 0 + return t == 0 case int16: - return k.(int16) == 0 + return t == 0 case int32: - return k.(int32) == 0 + return t == 0 case int64: - return k.(int64) == 0 + return t == 0 case uint: - return k.(uint) == 0 + return t == 0 case uint8: - return k.(uint8) == 0 + return t == 0 case uint16: - return k.(uint16) == 0 + return t == 0 case uint32: - return k.(uint32) == 0 + return t == 0 case uint64: - return k.(uint64) == 0 + return t == 0 case float32: - return k.(float32) == 0 + return t == 0 case float64: - return k.(float64) == 0 + return t == 0 case bool: - return k.(bool) == false + return !t case string: - return k.(string) == "" + return t == "" case *time.Time: - return k.(*time.Time) == nilTime || IsTimeZero(*k.(*time.Time)) + return t == nilTime || IsTimeZero(*t) case time.Time: - return IsTimeZero(k.(time.Time)) + return IsTimeZero(t) case Zeroable: return k.(Zeroable) == nil || k.(Zeroable).IsZero() case reflect.Value: // for go version less than 1.13 because reflect.Value has no method IsZero @@ -65,6 +66,7 @@ func IsZero(k interface{}) bool { var zeroType = reflect.TypeOf((*Zeroable)(nil)).Elem() +// IsValueZero returns true if the reflect Value is a zero func IsValueZero(v reflect.Value) bool { switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice: @@ -88,6 +90,7 @@ func IsValueZero(v reflect.Value) bool { return false } +// IsStructZero returns true if the Value is a struct and all fields is zero func IsStructZero(v reflect.Value) bool { if !v.IsValid() || v.NumField() == 0 { return true @@ -120,6 +123,7 @@ func IsStructZero(v reflect.Value) bool { return true } +// IsArrayZero returns true is a slice of array is zero func IsArrayZero(v reflect.Value) bool { if !v.IsValid() || v.Len() == 0 { return true @@ -134,11 +138,13 @@ func IsArrayZero(v reflect.Value) bool { return true } +// represents all zero times const ( ZeroTime0 = "0000-00-00 00:00:00" ZeroTime1 = "0001-01-01 00:00:00" ) +// IsTimeZero return true if a time is zero func IsTimeZero(t time.Time) bool { return t.IsZero() || t.Format("2006-01-02 15:04:05") == ZeroTime0 || t.Format("2006-01-02 15:04:05") == ZeroTime1 diff --git a/migrate/migrate.go b/migrate/migrate.go index 82c58f90..19b4afe0 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -110,10 +110,7 @@ func (m *Migrate) RollbackLast() error { return err } - if err := m.RollbackMigration(lastRunnedMigration); err != nil { - return err - } - return nil + return m.RollbackMigration(lastRunnedMigration) } func (m *Migrate) getLastRunnedMigration() (*Migration, error) { diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 19554f7e..3d7aa189 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -106,10 +106,7 @@ func TestInitSchema(t *testing.T) { if err := tx.Sync2(&Person{}); err != nil { return err } - if err := tx.Sync2(&Pet{}); err != nil { - return err - } - return nil + return tx.Sync2(&Pet{}) }) err = m.Migrate() diff --git a/session.go b/session.go index 17abd453..d5ccb6dc 100644 --- a/session.go +++ b/session.go @@ -169,6 +169,7 @@ func (session *Session) db() *core.DB { return session.engine.db } +// Engine returns session Engine func (session *Session) Engine() *Engine { return session.engine } @@ -895,7 +896,7 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) { } } -// ContextHook sets the context on this session +// Context sets the context on this session func (session *Session) Context(ctx context.Context) *Session { session.ctx = ctx return session diff --git a/session_get.go b/session_get.go index 6e65ea2f..e303176d 100644 --- a/session_get.go +++ b/session_get.go @@ -17,6 +17,7 @@ import ( ) var ( + // ErrObjectIsNil return error of object is nil ErrObjectIsNil = errors.New("object should not be nil") ) diff --git a/session_tx.go b/session_tx.go index 33ef72c6..8763784c 100644 --- a/session_tx.go +++ b/session_tx.go @@ -85,7 +85,7 @@ func (session *Session) Commit() error { return nil } -// if current session is in a transaction +// IsInTx if current session is in a transaction func (session *Session) IsInTx() bool { return !session.isAutoCommit } From 60cc3eaaf08848bad9c1f8c026482a26cda87ecb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 11 Apr 2021 16:47:04 +0800 Subject: [PATCH 025/179] Unsigned Support (#1889) This PR add support to MySQL, for other databases unsigned type will be downgrade to the related signed type. Replace #1810 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1889 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/mssql.go | 4 ++-- dialects/mysql.go | 29 ++++++++++++++++++-------- dialects/postgres.go | 8 ++++++-- dialects/sqlite3.go | 3 ++- go.mod | 4 ++-- go.sum | 4 ++-- integrations/types_test.go | 27 ++++++++++++++++++++++++ schemas/type.go | 42 +++++++++++++++++++++++--------------- 8 files changed, 87 insertions(+), 34 deletions(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index 083fb65d..15d1cd06 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -284,7 +284,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case schemas.MediumInt: + case schemas.MediumInt, schemas.UnsignedInt: res = schemas.Int case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: res = db.defaultVarchar + "(MAX)" @@ -296,7 +296,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TinyInt: res = schemas.TinyInt c.Length = 0 - case schemas.BigInt: + case schemas.BigInt, schemas.UnsignedBigInt: res = schemas.BigInt c.Length = 0 case schemas.NVarchar: diff --git a/dialects/mysql.go b/dialects/mysql.go index 769e30b1..2b530daf 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -254,6 +254,10 @@ func (db *mysql) SQLType(c *schemas.Column) string { c.Length = 40 case schemas.Json: res = schemas.Text + case schemas.UnsignedInt: + res = schemas.Int + case schemas.UnsignedBigInt: + res = schemas.BigInt default: res = t } @@ -271,6 +275,11 @@ func (db *mysql) SQLType(c *schemas.Column) string { } else if hasLen1 { res += "(" + strconv.Itoa(c.Length) + ")" } + + if c.SQLType.Name == schemas.UnsignedBigInt || c.SQLType.Name == schemas.UnsignedInt { + res += " UNSIGNED" + } + return res } @@ -331,16 +340,16 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName col := new(schemas.Column) col.Indexes = make(map[string]int) - var columnName, isNullable, colType, colKey, extra, comment string - var alreadyQuoted bool + var columnName, nullableStr, colType, colKey, extra, comment string + var alreadyQuoted, isUnsigned bool var colDefault *string - err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra, &comment, &alreadyQuoted) + err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &alreadyQuoted) if err != nil { return nil, nil, err } col.Name = strings.Trim(columnName, "` ") col.Comment = comment - if "YES" == isNullable { + if nullableStr == "YES" { col.Nullable = true } @@ -351,6 +360,11 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName col.DefaultIsEmpty = true } + fields := strings.Fields(colType) + if len(fields) == 2 && fields[1] == "unsigned" { + isUnsigned = true + } + colType = fields[0] cts := strings.Split(colType, "(") colName := cts[0] // Remove the /* mariadb-5.3 */ suffix from coltypes @@ -389,11 +403,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } } } - if colType == "FLOAT UNSIGNED" { - colType = "FLOAT" - } - if colType == "DOUBLE UNSIGNED" { - colType = "DOUBLE" + if isUnsigned { + colType = "UNSIGNED " + colType } col.Length = len1 col.Length2 = len2 diff --git a/dialects/postgres.go b/dialects/postgres.go index a2c0de74..2b234c66 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -833,12 +833,12 @@ func (db *postgres) SQLType(c *schemas.Column) string { case schemas.Bit: res = schemas.Boolean return res - case schemas.MediumInt, schemas.Int, schemas.Integer: + case schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedInt: if c.IsAutoIncrement { return schemas.Serial } return schemas.Integer - case schemas.BigInt: + case schemas.BigInt, schemas.UnsignedBigInt: if c.IsAutoIncrement { return schemas.BigSerial } @@ -1052,6 +1052,10 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A } } + if colDefault != nil && *colDefault == "unique_rowid()" { // ignore the system column added by cockroach + continue + } + col.Name = strings.Trim(colName, `" `) if colDefault != nil { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 62a38397..82683606 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -193,7 +193,8 @@ func (db *sqlite3) SQLType(c *schemas.Column) string { case schemas.Char, schemas.Varchar, schemas.NVarchar, schemas.TinyText, schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: return schemas.Text - case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt: + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, + schemas.UnsignedBigInt, schemas.UnsignedInt: return schemas.Integer case schemas.Float, schemas.Double, schemas.Real: return schemas.Real diff --git a/go.mod b/go.mod index b9eec3a0..5e073207 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module xorm.io/xorm -go 1.11 +go 1.13 require ( - github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc + github.com/denisenkom/go-mssqldb v0.9.0 github.com/go-sql-driver/mysql v1.5.0 github.com/lib/pq v1.7.0 github.com/mattn/go-sqlite3 v1.14.6 diff --git a/go.sum b/go.sum index 7c72c699..230c16aa 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGq gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= -github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= diff --git a/integrations/types_test.go b/integrations/types_test.go index 112308f3..d0357d6b 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -375,3 +375,30 @@ func TestCustomType2(t *testing.T) { fmt.Println(users) } + +func TestUnsigned(t *testing.T) { + type MyUnsignedStruct struct { + Id uint64 + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(MyUnsignedStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 1, len(tables[0].Columns())) + + switch testEngine.Dialect().URI().DBType { + case schemas.SQLITE: + assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) + case schemas.MYSQL: + assert.EqualValues(t, "UNSIGNED BIGINT", tables[0].Columns()[0].SQLType.Name) + case schemas.POSTGRES: + assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) + case schemas.MSSQL: + assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) + default: + assert.False(t, true, "Unsigned is not implemented") + } +} diff --git a/schemas/type.go b/schemas/type.go index f0ede296..6b50d184 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -73,13 +73,16 @@ func (s *SQLType) IsXML() bool { } var ( - Bit = "BIT" - TinyInt = "TINYINT" - SmallInt = "SMALLINT" - MediumInt = "MEDIUMINT" - Int = "INT" - Integer = "INTEGER" - BigInt = "BIGINT" + Bit = "BIT" + UnsignedBit = "UNSIGNED BIT" + TinyInt = "TINYINT" + SmallInt = "SMALLINT" + MediumInt = "MEDIUMINT" + Int = "INT" + UnsignedInt = "UNSIGNED INT" + Integer = "INTEGER" + BigInt = "BIGINT" + UnsignedBigInt = "UNSIGNED BIGINT" Enum = "ENUM" Set = "SET" @@ -136,13 +139,16 @@ var ( Array = "ARRAY" SqlTypes = map[string]int{ - Bit: NUMERIC_TYPE, - TinyInt: NUMERIC_TYPE, - SmallInt: NUMERIC_TYPE, - MediumInt: NUMERIC_TYPE, - Int: NUMERIC_TYPE, - Integer: NUMERIC_TYPE, - BigInt: NUMERIC_TYPE, + Bit: NUMERIC_TYPE, + UnsignedBit: NUMERIC_TYPE, + TinyInt: NUMERIC_TYPE, + SmallInt: NUMERIC_TYPE, + MediumInt: NUMERIC_TYPE, + Int: NUMERIC_TYPE, + UnsignedInt: NUMERIC_TYPE, + Integer: NUMERIC_TYPE, + BigInt: NUMERIC_TYPE, + UnsignedBigInt: NUMERIC_TYPE, Enum: TEXT_TYPE, Set: TEXT_TYPE, @@ -280,10 +286,14 @@ var ( // Type2SQLType generate SQLType acorrding Go's type func Type2SQLType(t reflect.Type) (st SQLType) { switch k := t.Kind(); k { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: st = SQLType{Int, 0, 0} - case reflect.Int64, reflect.Uint64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + st = SQLType{UnsignedInt, 0, 0} + case reflect.Int64: st = SQLType{BigInt, 0, 0} + case reflect.Uint64: + st = SQLType{UnsignedBigInt, 0, 0} case reflect.Float32: st = SQLType{Float, 0, 0} case reflect.Float64: From 1ade49614b5a68ecfafe38e447d62ec312470805 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 11 Apr 2021 23:41:20 +0800 Subject: [PATCH 026/179] More tests (#1890) replace #1585 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1890 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/tags_test.go | 77 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/integrations/tags_test.go b/integrations/tags_test.go index f787fffe..fc7b505e 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -757,6 +757,8 @@ func TestAutoIncrTag(t *testing.T) { assert.True(t, cols[0].IsAutoIncrement) assert.True(t, cols[0].IsPrimaryKey) assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) type TestAutoIncr2 struct { Id int64 `xorm:"id"` @@ -770,6 +772,8 @@ func TestAutoIncrTag(t *testing.T) { assert.False(t, cols[0].IsAutoIncrement) assert.False(t, cols[0].IsPrimaryKey) assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) type TestAutoIncr3 struct { Id int64 `xorm:"'ID'"` @@ -783,6 +787,8 @@ func TestAutoIncrTag(t *testing.T) { assert.False(t, cols[0].IsAutoIncrement) assert.False(t, cols[0].IsPrimaryKey) assert.Equal(t, "ID", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) type TestAutoIncr4 struct { Id int64 `xorm:"pk"` @@ -796,6 +802,8 @@ func TestAutoIncrTag(t *testing.T) { assert.False(t, cols[0].IsAutoIncrement) assert.True(t, cols[0].IsPrimaryKey) assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) } func TestTagComment(t *testing.T) { @@ -809,6 +817,16 @@ func TestTagComment(t *testing.T) { Id int64 `xorm:"comment(主键)"` } + tb, err := testEngine.TableInfo(new(TestComment1)) + assert.NoError(t, err) + cols := tb.Columns() + assert.EqualValues(t, 1, len(cols)) + assert.False(t, cols[0].IsAutoIncrement) + assert.False(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + assert.NoError(t, testEngine.Sync2(new(TestComment1))) tables, err := testEngine.DBMetas() @@ -823,6 +841,16 @@ func TestTagComment(t *testing.T) { Id int64 `xorm:"comment('主键')"` } + tb, err = testEngine.TableInfo(new(TestComment2)) + assert.NoError(t, err) + cols = tb.Columns() + assert.EqualValues(t, 1, len(cols)) + assert.False(t, cols[0].IsAutoIncrement) + assert.False(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + assert.NoError(t, testEngine.Sync2(new(TestComment2))) tables, err = testEngine.DBMetas() @@ -841,6 +869,28 @@ func TestTagDefault(t *testing.T) { Age int `xorm:"default(10)"` } + tb, err := testEngine.TableInfo(new(DefaultStruct)) + assert.NoError(t, err) + cols := tb.Columns() + assert.EqualValues(t, 3, len(cols)) + assert.True(t, cols[0].IsAutoIncrement) + assert.True(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + + assert.False(t, cols[1].IsAutoIncrement) + assert.False(t, cols[1].IsPrimaryKey) + assert.Equal(t, "name", cols[1].Name) + assert.True(t, cols[1].DefaultIsEmpty) + assert.EqualValues(t, "", cols[1].Default) + + assert.False(t, cols[2].IsAutoIncrement) + assert.False(t, cols[2].IsPrimaryKey) + assert.Equal(t, "age", cols[2].Name) + assert.False(t, cols[2].DefaultIsEmpty) + assert.EqualValues(t, "10", cols[2].Default) + assertSync(t, new(DefaultStruct)) tables, err := testEngine.DBMetas() @@ -880,10 +930,33 @@ func TestTagDefault2(t *testing.T) { assert.NoError(t, PrepareEngine()) type DefaultStruct2 struct { - Id int64 - Name string + Id int64 + Name string + NullDefault string `xorm:"default('NULL')"` } + tb, err := testEngine.TableInfo(new(DefaultStruct2)) + assert.NoError(t, err) + cols := tb.Columns() + assert.EqualValues(t, 3, len(cols)) + assert.True(t, cols[0].IsAutoIncrement) + assert.True(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + + assert.False(t, cols[1].IsAutoIncrement) + assert.False(t, cols[1].IsPrimaryKey) + assert.Equal(t, "name", cols[1].Name) + assert.True(t, cols[1].DefaultIsEmpty) + assert.EqualValues(t, "", cols[1].Default) + + assert.False(t, cols[2].IsAutoIncrement) + assert.False(t, cols[2].IsPrimaryKey) + assert.Equal(t, "null_default", cols[2].Name) + assert.False(t, cols[2].DefaultIsEmpty) + assert.EqualValues(t, "'NULL'", cols[2].Default) + assertSync(t, new(DefaultStruct2)) tables, err := testEngine.DBMetas() From bd9535d781a781aaa49019a489ac3e568ec9c77a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 12 Apr 2021 09:19:08 +0800 Subject: [PATCH 027/179] Fix comments (#1893) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1893 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .revive.toml | 4 +++- caches/encode.go | 9 ++++++++- core/tx.go | 25 +++++++++++++++++++++++++ dialects/driver.go | 8 ++++++-- dialects/filter.go | 1 + dialects/time.go | 1 + integrations/tests.go | 5 +---- internal/statements/expr_param.go | 1 + internal/statements/statement.go | 22 ++++++++++++++++++---- schemas/type.go | 12 ++++++++++++ 10 files changed, 76 insertions(+), 12 deletions(-) diff --git a/.revive.toml b/.revive.toml index 64e223bb..6dec7465 100644 --- a/.revive.toml +++ b/.revive.toml @@ -15,6 +15,7 @@ warningCode = 1 [rule.if-return] [rule.increment-decrement] [rule.var-naming] + arguments = [["ID", "UID", "UUID", "URL", "JSON"], []] [rule.var-declaration] [rule.package-comments] [rule.range] @@ -22,4 +23,5 @@ warningCode = 1 [rule.time-naming] [rule.unexported-return] [rule.indent-error-flow] -[rule.errorf] \ No newline at end of file +[rule.errorf] +[rule.struct-tag] \ No newline at end of file diff --git a/caches/encode.go b/caches/encode.go index 4ba39924..95536d7e 100644 --- a/caches/encode.go +++ b/caches/encode.go @@ -13,22 +13,26 @@ import ( "io" ) -// md5 hash string +// Md5 return md5 hash string func Md5(str string) string { m := md5.New() io.WriteString(m, str) return fmt.Sprintf("%x", m.Sum(nil)) } + +// Encode Encode data func Encode(data interface{}) ([]byte, error) { //return JsonEncode(data) return GobEncode(data) } +// Decode decode data func Decode(data []byte, to interface{}) error { //return JsonDecode(data, to) return GobDecode(data, to) } +// GobEncode encode data with gob func GobEncode(data interface{}) ([]byte, error) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) @@ -39,12 +43,14 @@ func GobEncode(data interface{}) ([]byte, error) { return buf.Bytes(), nil } +// GobDecode decode data with gob func GobDecode(data []byte, to interface{}) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) return dec.Decode(to) } +// JsonEncode encode data with json func JsonEncode(data interface{}) ([]byte, error) { val, err := json.Marshal(data) if err != nil { @@ -53,6 +59,7 @@ func JsonEncode(data interface{}) ([]byte, error) { return val, nil } +// JsonDecode decode data with json func JsonDecode(data []byte, to interface{}) error { return json.Unmarshal(data, to) } diff --git a/core/tx.go b/core/tx.go index a85a6874..24d548b3 100644 --- a/core/tx.go +++ b/core/tx.go @@ -22,6 +22,7 @@ type Tx struct { ctx context.Context } +// BeginTx begin a transaction with option func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { hookCtx := contexts.NewContextHook(ctx, "BEGIN TRANSACTION", nil) ctx, err := db.beforeProcess(hookCtx) @@ -36,10 +37,12 @@ func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { return &Tx{tx, db, ctx}, nil } +// Begin begins a transaction func (db *DB) Begin() (*Tx, error) { return db.BeginTx(context.Background(), nil) } +// Commit submit the transaction func (tx *Tx) Commit() error { hookCtx := contexts.NewContextHook(tx.ctx, "COMMIT", nil) ctx, err := tx.db.beforeProcess(hookCtx) @@ -54,6 +57,7 @@ func (tx *Tx) Commit() error { return nil } +// Rollback rollback the transaction func (tx *Tx) Rollback() error { hookCtx := contexts.NewContextHook(tx.ctx, "ROLLBACK", nil) ctx, err := tx.db.beforeProcess(hookCtx) @@ -68,6 +72,7 @@ func (tx *Tx) Rollback() error { return nil } +// PrepareContext prepare the query func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { names := make(map[string]int) var i int @@ -89,19 +94,23 @@ func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { return &Stmt{stmt, tx.db, names, query}, nil } +// Prepare prepare the query func (tx *Tx) Prepare(query string) (*Stmt, error) { return tx.PrepareContext(context.Background(), query) } +// StmtContext creates Stmt with context func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt { stmt.Stmt = tx.Tx.StmtContext(ctx, stmt.Stmt) return stmt } +// Stmt creates Stmt func (tx *Tx) Stmt(stmt *Stmt) *Stmt { return tx.StmtContext(context.Background(), stmt) } +// ExecMapContext executes query with args in a map func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { query, args, err := MapToSlice(query, mp) if err != nil { @@ -110,10 +119,12 @@ func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) return tx.ExecContext(ctx, query, args...) } +// ExecMap executes query with args in a map func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { return tx.ExecMapContext(context.Background(), query, mp) } +// ExecStructContext executes query with args in a struct func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -122,6 +133,7 @@ func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{ return tx.ExecContext(ctx, query, args...) } +// ExecContext executes a query with args func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { hookCtx := contexts.NewContextHook(ctx, query, args) ctx, err := tx.db.beforeProcess(hookCtx) @@ -136,10 +148,12 @@ func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{} return res, err } +// ExecStruct executes query with args in a struct func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { return tx.ExecStructContext(context.Background(), query, st) } +// QueryContext query with args func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { hookCtx := contexts.NewContextHook(ctx, query, args) ctx, err := tx.db.beforeProcess(hookCtx) @@ -157,10 +171,12 @@ func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{ return &Rows{rows, tx.db}, nil } +// Query query with args func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { return tx.QueryContext(context.Background(), query, args...) } +// QueryMapContext query with args in a map func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) { query, args, err := MapToSlice(query, mp) if err != nil { @@ -169,10 +185,12 @@ func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) return tx.QueryContext(ctx, query, args...) } +// QueryMap query with args in a map func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { return tx.QueryMapContext(context.Background(), query, mp) } +// QueryStructContext query with args in struct func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -181,19 +199,23 @@ func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface return tx.QueryContext(ctx, query, args...) } +// QueryStruct query with args in struct func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { return tx.QueryStructContext(context.Background(), query, st) } +// QueryRowContext query one row with args func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { rows, err := tx.QueryContext(ctx, query, args...) return &Row{rows, err} } +// QueryRow query one row with args func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { return tx.QueryRowContext(context.Background(), query, args...) } +// QueryRowMapContext query one row with args in a map func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { query, args, err := MapToSlice(query, mp) if err != nil { @@ -202,10 +224,12 @@ func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface return tx.QueryRowContext(ctx, query, args...) } +// QueryRowMap query one row with args in a map func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { return tx.QueryRowMapContext(context.Background(), query, mp) } +// QueryRowStructContext query one row with args in struct func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { query, args, err := StructToSlice(query, st) if err != nil { @@ -214,6 +238,7 @@ func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interf return tx.QueryRowContext(ctx, query, args...) } +// QueryRowStruct query one row with args in struct func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { return tx.QueryRowStructContext(context.Background(), query, st) } diff --git a/dialects/driver.go b/dialects/driver.go index ae3afe42..bb46a936 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -8,6 +8,7 @@ import ( "fmt" ) +// Driver represents a database driver type Driver interface { Parse(string, string) (*URI, error) } @@ -16,6 +17,7 @@ var ( drivers = map[string]Driver{} ) +// RegisterDriver register a driver func RegisterDriver(driverName string, driver Driver) { if driver == nil { panic("core: Register driver is nil") @@ -26,10 +28,12 @@ func RegisterDriver(driverName string, driver Driver) { drivers[driverName] = driver } +// QueryDriver query a driver with name func QueryDriver(driverName string) Driver { return drivers[driverName] } +// RegisteredDriverSize returned all drivers's length func RegisteredDriverSize() int { return len(drivers) } @@ -38,7 +42,7 @@ func RegisteredDriverSize() int { func OpenDialect(driverName, connstr string) (Dialect, error) { driver := QueryDriver(driverName) if driver == nil { - return nil, fmt.Errorf("Unsupported driver name: %v", driverName) + return nil, fmt.Errorf("unsupported driver name: %v", driverName) } uri, err := driver.Parse(driverName, connstr) @@ -48,7 +52,7 @@ func OpenDialect(driverName, connstr string) (Dialect, error) { dialect := QueryDialect(uri.DBType) if dialect == nil { - return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DBType) + return nil, fmt.Errorf("unsupported dialect type: %v", uri.DBType) } dialect.Init(uri) diff --git a/dialects/filter.go b/dialects/filter.go index 6968b6ce..2a36a731 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -38,6 +38,7 @@ func convertQuestionMark(sql, prefix string, start int) string { return buf.String() } +// Do implements Filter func (s *SeqFilter) Do(sql string) string { return convertQuestionMark(sql, s.Prefix, s.Start) } diff --git a/dialects/time.go b/dialects/time.go index 9a3c82a4..5aee0c10 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -38,6 +38,7 @@ func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{} return } +// FormatColumnTime format column time func FormatColumnTime(dialect Dialect, defaultTimeZone *time.Location, col *schemas.Column, t time.Time) (v interface{}) { if t.IsZero() { if col.Nullable { diff --git a/integrations/tests.go b/integrations/tests.go index 512f3962..8b14b0f4 100644 --- a/integrations/tests.go +++ b/integrations/tests.go @@ -166,10 +166,7 @@ func createEngine(dbType, connStr string) error { for _, table := range tables { tableNames = append(tableNames, table.Name) } - if err = testEngine.DropTables(tableNames...); err != nil { - return err - } - return nil + return testEngine.DropTables(tableNames...) } // PrepareEngine prepare tests ORM engine diff --git a/internal/statements/expr_param.go b/internal/statements/expr_param.go index 6657408e..d0c355d3 100644 --- a/internal/statements/expr_param.go +++ b/internal/statements/expr_param.go @@ -12,6 +12,7 @@ import ( "xorm.io/xorm/schemas" ) +// ErrUnsupportedExprType represents an error with unsupported express type type ErrUnsupportedExprType struct { tp string } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index a4294bec..87f785ae 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -103,6 +103,7 @@ func (statement *Statement) GenRawSQL() string { return statement.ReplaceQuote(statement.RawSQL) } +// GenCondSQL generates condition SQL func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []interface{}, error) { condSQL, condArgs, err := builder.ToSQL(condOrBuilder) if err != nil { @@ -119,11 +120,12 @@ func (statement *Statement) ReplaceQuote(sql string) string { return statement.dialect.Quoter().Replace(sql) } +// SetContextCache sets context cache func (statement *Statement) SetContextCache(ctxCache contexts.ContextCache) { statement.Context = ctxCache } -// Init reset all the statement's fields +// Reset reset all the statement's fields func (statement *Statement) Reset() { statement.RefTable = nil statement.Start = 0 @@ -163,7 +165,7 @@ func (statement *Statement) Reset() { statement.LastError = nil } -// NoAutoCondition if you do not want convert bean's field as query condition, then use this function +// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { statement.NoAutoCondition = true if len(no) > 0 { @@ -271,6 +273,7 @@ func (statement *Statement) NotIn(column string, args ...interface{}) *Statement return statement } +// SetRefValue set ref value func (statement *Statement) SetRefValue(v reflect.Value) error { var err error statement.RefTable, err = statement.tagParser.ParseWithCache(reflect.Indirect(v)) @@ -285,6 +288,7 @@ func rValue(bean interface{}) reflect.Value { return reflect.Indirect(reflect.ValueOf(bean)) } +// SetRefBean set ref bean func (statement *Statement) SetRefBean(bean interface{}) error { var err error statement.RefTable, err = statement.tagParser.ParseWithCache(rValue(bean)) @@ -390,6 +394,7 @@ func (statement *Statement) Cols(columns ...string) *Statement { return statement } +// ColumnStr returns column string func (statement *Statement) ColumnStr() string { return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") } @@ -493,11 +498,12 @@ func (statement *Statement) Asc(colNames ...string) *Statement { return statement } +// Conds returns condtions func (statement *Statement) Conds() builder.Cond { return statement.cond } -// Table tempororily set table name, the parameter could be a string or a pointer of struct +// SetTable tempororily set table name, the parameter could be a string or a pointer of struct func (statement *Statement) SetTable(tableNameOrBean interface{}) error { v := rValue(tableNameOrBean) t := v.Type() @@ -564,7 +570,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition return statement } -// tbName get some table's table name +// tbNameNoSchema get some table's table name func (statement *Statement) tbNameNoSchema(table *schemas.Table) string { if len(statement.AltTableName) > 0 { return statement.AltTableName @@ -591,6 +597,7 @@ func (statement *Statement) SetUnscoped() *Statement { return statement } +// GetUnscoped return true if it's unscoped func (statement *Statement) GetUnscoped() bool { return statement.unscoped } @@ -636,6 +643,7 @@ func (statement *Statement) genColumnStr() string { return buf.String() } +// GenCreateTableSQL generated create table SQL func (statement *Statement) GenCreateTableSQL() []string { statement.RefTable.StoreEngine = statement.StoreEngine statement.RefTable.Charset = statement.Charset @@ -643,6 +651,7 @@ func (statement *Statement) GenCreateTableSQL() []string { return s } +// GenIndexSQL generated create index SQL func (statement *Statement) GenIndexSQL() []string { var sqls []string tbName := statement.TableName() @@ -659,6 +668,7 @@ func uniqueName(tableName, uqeName string) string { return fmt.Sprintf("UQE_%v_%v", tableName, uqeName) } +// GenUniqueSQL generates unique SQL func (statement *Statement) GenUniqueSQL() []string { var sqls []string tbName := statement.TableName() @@ -671,6 +681,7 @@ func (statement *Statement) GenUniqueSQL() []string { return sqls } +// GenDelIndexSQL generate delete index SQL func (statement *Statement) GenDelIndexSQL() []string { var sqls []string tbName := statement.TableName() @@ -896,6 +907,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, return builder.And(conds...), nil } +// BuildConds builds condition func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { return statement.buildConds2(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, statement.unscoped, statement.MustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) @@ -917,6 +929,7 @@ func (statement *Statement) mergeConds(bean interface{}) error { return nil } +// GenConds generates conditions func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, error) { if err := statement.mergeConds(bean); err != nil { return "", nil, err @@ -930,6 +943,7 @@ func (statement *Statement) quoteColumnStr(columnStr string) string { return statement.dialect.Quoter().Join(columns, ",") } +// ConvertSQLOrArgs converts sql or args func (statement *Statement) ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { sql, args, err := convertSQLOrArgs(sqlOrArgs...) if err != nil { diff --git a/schemas/type.go b/schemas/type.go index 6b50d184..c6cdfb87 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -11,8 +11,10 @@ import ( "time" ) +// DBType represents a database type type DBType string +// enumerates all database types const ( POSTGRES DBType = "postgres" SQLITE DBType = "sqlite3" @@ -28,6 +30,7 @@ type SQLType struct { DefaultLength2 int } +// enumerates all columns types const ( UNKNOW_TYPE = iota TEXT_TYPE @@ -37,6 +40,7 @@ const ( ARRAY_TYPE ) +// IsType reutrns ture if the column type is the same as the parameter func (s *SQLType) IsType(st int) bool { if t, ok := SqlTypes[s.Name]; ok && t == st { return true @@ -44,34 +48,42 @@ func (s *SQLType) IsType(st int) bool { return false } +// IsText returns true if column is a text type func (s *SQLType) IsText() bool { return s.IsType(TEXT_TYPE) } +// IsBlob returns true if column is a binary type func (s *SQLType) IsBlob() bool { return s.IsType(BLOB_TYPE) } +// IsTime returns true if column is a time type func (s *SQLType) IsTime() bool { return s.IsType(TIME_TYPE) } +// IsNumeric returns true if column is a numeric type func (s *SQLType) IsNumeric() bool { return s.IsType(NUMERIC_TYPE) } +// IsArray returns true if column is an array type func (s *SQLType) IsArray() bool { return s.IsType(ARRAY_TYPE) } +// IsJson returns true if column is an array type func (s *SQLType) IsJson() bool { return s.Name == Json || s.Name == Jsonb } +// IsXML returns true if column is an xml type func (s *SQLType) IsXML() bool { return s.Name == XML } +// enumerates all the database column types var ( Bit = "BIT" UnsignedBit = "UNSIGNED BIT" From ce2a743e8835469b414f4bde433e178198b7ee9b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 12 Apr 2021 16:00:07 +0800 Subject: [PATCH 028/179] Fix comments (#1896) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1896 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- core/db.go | 15 +++++ core/db_test.go | 34 ++++++------ core/rows.go | 20 +++++-- core/scan.go | 4 ++ core/stmt.go | 19 +++++++ core/tx.go | 10 +--- integrations/session_delete_test.go | 2 + internal/statements/cache.go | 2 + internal/statements/query.go | 5 ++ internal/statements/statement.go | 13 ++--- internal/statements/statement_args.go | 2 + names/mapper.go | 18 +++++- names/table_name.go | 1 + schemas/column.go | 1 + schemas/index.go | 6 +- schemas/pk.go | 5 ++ schemas/quote.go | 8 ++- schemas/table.go | 5 ++ schemas/type.go | 80 ++++++++++++++------------- tags/parser.go | 9 +++ 20 files changed, 174 insertions(+), 85 deletions(-) diff --git a/core/db.go b/core/db.go index 50c64c6f..ef5ab227 100644 --- a/core/db.go +++ b/core/db.go @@ -23,6 +23,7 @@ var ( DefaultCacheSize = 200 ) +// MapToSlice map query and struct as sql and args func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -44,6 +45,7 @@ func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { return query, args, err } +// StructToSlice converts a query and struct as sql and args func StructToSlice(query string, st interface{}) (string, []interface{}, error) { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -176,6 +178,7 @@ func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { return db.QueryMapContext(context.Background(), query, mp) } +// QueryStructContext query rows with struct func (db *DB) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -184,10 +187,12 @@ func (db *DB) QueryStructContext(ctx context.Context, query string, st interface return db.QueryContext(ctx, query, args...) } +// QueryStruct query rows with struct func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { return db.QueryStructContext(context.Background(), query, st) } +// QueryRowContext query row with args func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { rows, err := db.QueryContext(ctx, query, args...) if err != nil { @@ -196,10 +201,12 @@ func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interfa return &Row{rows, nil} } +// QueryRow query row with args func (db *DB) QueryRow(query string, args ...interface{}) *Row { return db.QueryRowContext(context.Background(), query, args...) } +// QueryRowMapContext query row with map func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { query, args, err := MapToSlice(query, mp) if err != nil { @@ -208,10 +215,12 @@ func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface return db.QueryRowContext(ctx, query, args...) } +// QueryRowMap query row with map func (db *DB) QueryRowMap(query string, mp interface{}) *Row { return db.QueryRowMapContext(context.Background(), query, mp) } +// QueryRowStructContext query row with struct func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { query, args, err := StructToSlice(query, st) if err != nil { @@ -220,6 +229,7 @@ func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interf return db.QueryRowContext(ctx, query, args...) } +// QueryRowStruct query row with struct func (db *DB) QueryRowStruct(query string, st interface{}) *Row { return db.QueryRowStructContext(context.Background(), query, st) } @@ -239,10 +249,12 @@ func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) return db.ExecContext(ctx, query, args...) } +// ExecMap exec query with map func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { return db.ExecMapContext(context.Background(), query, mp) } +// ExecStructContext exec query with map func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -251,6 +263,7 @@ func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{ return db.ExecContext(ctx, query, args...) } +// ExecContext exec query with args func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { hookCtx := contexts.NewContextHook(ctx, query, args) ctx, err := db.beforeProcess(hookCtx) @@ -265,6 +278,7 @@ func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{} return res, nil } +// ExecStruct exec query with struct func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { return db.ExecStructContext(context.Background(), query, st) } @@ -288,6 +302,7 @@ func (db *DB) afterProcess(c *contexts.ContextHook) error { return err } +// AddHook adds hook func (db *DB) AddHook(h ...contexts.Hook) { db.hooks.AddHook(h...) } diff --git a/core/db_test.go b/core/db_test.go index 104c5b95..e9c2d82d 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -21,7 +21,7 @@ import ( var ( dbtype = flag.String("dbtype", "sqlite3", "database type") dbConn = flag.String("dbConn", "./db_test.db", "database connect string") - createTableSql string + createTableSQL string ) func TestMain(m *testing.M) { @@ -29,12 +29,12 @@ func TestMain(m *testing.M) { switch *dbtype { case "sqlite3", "sqlite": - createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " + + createTableSQL = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" case "mysql": fallthrough default: - createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` TEXT NULL, " + + createTableSQL = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` TEXT NULL, " + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" } @@ -66,7 +66,7 @@ func BenchmarkOriQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -121,7 +121,7 @@ func BenchmarkStructQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -166,7 +166,7 @@ func BenchmarkStruct2Query(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -212,7 +212,7 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -270,7 +270,7 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -321,7 +321,7 @@ func BenchmarkSliceStringQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -372,7 +372,7 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -426,7 +426,7 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -473,7 +473,7 @@ func BenchmarkMapStringQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -519,7 +519,7 @@ func BenchmarkExec(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -544,7 +544,7 @@ func BenchmarkExecMap(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -577,7 +577,7 @@ func TestExecMap(t *testing.T) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { t.Error(err) } @@ -620,7 +620,7 @@ func TestExecStruct(t *testing.T) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { t.Error(err) } @@ -663,7 +663,7 @@ func BenchmarkExecStruct(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } diff --git a/core/rows.go b/core/rows.go index a1e8bfbc..c15a59a3 100644 --- a/core/rows.go +++ b/core/rows.go @@ -11,11 +11,13 @@ import ( "sync" ) +// Rows represents rows of table type Rows struct { *sql.Rows db *DB } +// ToMapString returns all records func (rs *Rows) ToMapString() ([]map[string]string, error) { cols, err := rs.Columns() if err != nil { @@ -34,7 +36,7 @@ func (rs *Rows) ToMapString() ([]map[string]string, error) { return results, nil } -// scan data to a struct's pointer according field index +// ScanStructByIndex scan data to a struct's pointer according field index func (rs *Rows) ScanStructByIndex(dest ...interface{}) error { if len(dest) == 0 { return errors.New("at least one struct") @@ -94,7 +96,7 @@ func fieldByName(v reflect.Value, name string) reflect.Value { return reflect.Zero(t) } -// scan data to a struct's pointer according field name +// ScanStructByName scan data to a struct's pointer according field name func (rs *Rows) ScanStructByName(dest interface{}) error { vv := reflect.ValueOf(dest) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -120,7 +122,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error { return rs.Rows.Scan(newDest...) } -// scan data to a slice's pointer, slice's length should equal to columns' number +// ScanSlice scan data to a slice's pointer, slice's length should equal to columns' number func (rs *Rows) ScanSlice(dest interface{}) error { vv := reflect.ValueOf(dest) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Slice { @@ -155,7 +157,7 @@ func (rs *Rows) ScanSlice(dest interface{}) error { return nil } -// scan data to a map's pointer +// ScanMap scan data to a map's pointer func (rs *Rows) ScanMap(dest interface{}) error { vv := reflect.ValueOf(dest) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -187,6 +189,7 @@ func (rs *Rows) ScanMap(dest interface{}) error { return nil } +// Row reprents a row of a tab type Row struct { rows *Rows // One of these two will be non-nil: @@ -205,6 +208,7 @@ func NewRow(rows *Rows, err error) *Row { return &Row{rows, err} } +// Columns returns all columns of the row func (row *Row) Columns() ([]string, error) { if row.err != nil { return nil, row.err @@ -212,6 +216,7 @@ func (row *Row) Columns() ([]string, error) { return row.rows.Columns() } +// Scan retrieves all row column values func (row *Row) Scan(dest ...interface{}) error { if row.err != nil { return row.err @@ -238,6 +243,7 @@ func (row *Row) Scan(dest ...interface{}) error { return row.rows.Close() } +// ScanStructByName retrieves all row column values into a struct func (row *Row) ScanStructByName(dest interface{}) error { if row.err != nil { return row.err @@ -258,6 +264,7 @@ func (row *Row) ScanStructByName(dest interface{}) error { return row.rows.Close() } +// ScanStructByIndex retrieves all row column values into a struct func (row *Row) ScanStructByIndex(dest interface{}) error { if row.err != nil { return row.err @@ -278,7 +285,7 @@ func (row *Row) ScanStructByIndex(dest interface{}) error { return row.rows.Close() } -// scan data to a slice's pointer, slice's length should equal to columns' number +// ScanSlice scan data to a slice's pointer, slice's length should equal to columns' number func (row *Row) ScanSlice(dest interface{}) error { if row.err != nil { return row.err @@ -300,7 +307,7 @@ func (row *Row) ScanSlice(dest interface{}) error { return row.rows.Close() } -// scan data to a map's pointer +// ScanMap scan data to a map's pointer func (row *Row) ScanMap(dest interface{}) error { if row.err != nil { return row.err @@ -322,6 +329,7 @@ func (row *Row) ScanMap(dest interface{}) error { return row.rows.Close() } +// ToMapString returns all clumns of this record func (row *Row) ToMapString() (map[string]string, error) { cols, err := row.Columns() if err != nil { diff --git a/core/scan.go b/core/scan.go index 897b5341..1e7e4525 100644 --- a/core/scan.go +++ b/core/scan.go @@ -10,12 +10,14 @@ import ( "time" ) +// NullTime defines a customize type NullTime type NullTime time.Time var ( _ driver.Valuer = NullTime{} ) +// Scan implements driver.Valuer func (ns *NullTime) Scan(value interface{}) error { if value == nil { return nil @@ -58,9 +60,11 @@ func convertTime(dest *NullTime, src interface{}) error { return nil } +// EmptyScanner represents an empty scanner type EmptyScanner struct { } +// Scan implements func (EmptyScanner) Scan(src interface{}) error { return nil } diff --git a/core/stmt.go b/core/stmt.go index d46ac9c6..260843d5 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -21,6 +21,7 @@ type Stmt struct { query string } +// PrepareContext creates a prepare statement func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { names := make(map[string]int) var i int @@ -42,10 +43,12 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { return &Stmt{stmt, db, names, query}, nil } +// Prepare creates a prepare statement func (db *DB) Prepare(query string) (*Stmt, error) { return db.PrepareContext(context.Background(), query) } +// ExecMapContext execute with map func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -59,10 +62,12 @@ func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, return s.ExecContext(ctx, args...) } +// ExecMap executes with map func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { return s.ExecMapContext(context.Background(), mp) } +// ExecStructContext executes with struct func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Result, error) { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -76,10 +81,12 @@ func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Resul return s.ExecContext(ctx, args...) } +// ExecStruct executes with struct func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { return s.ExecStructContext(context.Background(), st) } +// ExecContext with args func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { hookCtx := contexts.NewContextHook(ctx, s.query, args) ctx, err := s.db.beforeProcess(hookCtx) @@ -94,6 +101,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result return res, nil } +// QueryContext query with args func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { hookCtx := contexts.NewContextHook(ctx, s.query, args) ctx, err := s.db.beforeProcess(hookCtx) @@ -108,10 +116,12 @@ func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, er return &Rows{rows, s.db}, nil } +// Query query with args func (s *Stmt) Query(args ...interface{}) (*Rows, error) { return s.QueryContext(context.Background(), args...) } +// QueryMapContext query with map func (s *Stmt) QueryMapContext(ctx context.Context, mp interface{}) (*Rows, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -126,10 +136,12 @@ func (s *Stmt) QueryMapContext(ctx context.Context, mp interface{}) (*Rows, erro return s.QueryContext(ctx, args...) } +// QueryMap query with map func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { return s.QueryMapContext(context.Background(), mp) } +// QueryStructContext query with struct func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, error) { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -144,19 +156,23 @@ func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, e return s.QueryContext(ctx, args...) } +// QueryStruct query with struct func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { return s.QueryStructContext(context.Background(), st) } +// QueryRowContext query row with args func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row { rows, err := s.QueryContext(ctx, args...) return &Row{rows, err} } +// QueryRow query row with args func (s *Stmt) QueryRow(args ...interface{}) *Row { return s.QueryRowContext(context.Background(), args...) } +// QueryRowMapContext query row with map func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -171,10 +187,12 @@ func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row { return s.QueryRowContext(ctx, args...) } +// QueryRowMap query row with map func (s *Stmt) QueryRowMap(mp interface{}) *Row { return s.QueryRowMapContext(context.Background(), mp) } +// QueryRowStructContext query row with struct func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -189,6 +207,7 @@ func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row { return s.QueryRowContext(ctx, args...) } +// QueryRowStruct query row with struct func (s *Stmt) QueryRowStruct(st interface{}) *Row { return s.QueryRowStructContext(context.Background(), st) } diff --git a/core/tx.go b/core/tx.go index 24d548b3..a2f745f8 100644 --- a/core/tx.go +++ b/core/tx.go @@ -51,10 +51,7 @@ func (tx *Tx) Commit() error { } err = tx.Tx.Commit() hookCtx.End(ctx, nil, err) - if err := tx.db.afterProcess(hookCtx); err != nil { - return err - } - return nil + return tx.db.afterProcess(hookCtx) } // Rollback rollback the transaction @@ -66,10 +63,7 @@ func (tx *Tx) Rollback() error { } err = tx.Tx.Rollback() hookCtx.End(ctx, nil, err) - if err := tx.db.afterProcess(hookCtx); err != nil { - return err - } - return nil + return tx.db.afterProcess(hookCtx) } // PrepareContext prepare the query diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index f3565963..cc7e861d 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -97,6 +97,7 @@ func TestDeleted(t *testing.T) { // Test normal Find() var records1 []Deleted err = testEngine.Where("`"+testEngine.GetColumnMapper().Obj2Table("Id")+"` > 0").Find(&records1, &Deleted{}) + assert.NoError(t, err) assert.EqualValues(t, 3, len(records1)) // Test normal Get() @@ -132,6 +133,7 @@ func TestDeleted(t *testing.T) { record2 := &Deleted{} has, err = testEngine.ID(2).Get(record2) assert.NoError(t, err) + assert.True(t, has) assert.True(t, record2.DeletedAt.IsZero()) // Test find all records whatever `deleted`. diff --git a/internal/statements/cache.go b/internal/statements/cache.go index cb33df08..669cd018 100644 --- a/internal/statements/cache.go +++ b/internal/statements/cache.go @@ -12,6 +12,7 @@ import ( "xorm.io/xorm/schemas" ) +// ConvertIDSQL converts SQL with id func (statement *Statement) ConvertIDSQL(sqlStr string) string { if statement.RefTable != nil { cols := statement.RefTable.PKColumns() @@ -37,6 +38,7 @@ func (statement *Statement) ConvertIDSQL(sqlStr string) string { return "" } +// ConvertUpdateSQL converts update SQL func (statement *Statement) ConvertUpdateSQL(sqlStr string) (string, string) { if statement.RefTable == nil || len(statement.RefTable.PrimaryKeys) != 1 { return "", "" diff --git a/internal/statements/query.go b/internal/statements/query.go index ab3021bf..f1b36770 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -14,6 +14,7 @@ import ( "xorm.io/xorm/schemas" ) +// GenQuerySQL generate query SQL func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { if len(sqlOrArgs) > 0 { return statement.ConvertSQLOrArgs(sqlOrArgs...) @@ -72,6 +73,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return sqlStr, args, nil } +// GenSumSQL generates sum SQL func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil @@ -102,6 +104,7 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri return sqlStr, append(statement.joinArgs, condArgs...), nil } +// GenGetSQL generates Get SQL func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, error) { v := rValue(bean) isStruct := v.Kind() == reflect.Struct @@ -316,6 +319,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB return buf.String(), condArgs, nil } +// GenExistSQL generates Exist SQL func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil @@ -385,6 +389,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac return sqlStr, args, nil } +// GenFindSQL generates Find SQL func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 87f785ae..3dd036a6 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -90,14 +90,11 @@ func NewStatement(dialect dialects.Dialect, tagParser *tags.Parser, defaultTimeZ return statement } +// SetTableName set table name func (statement *Statement) SetTableName(tableName string) { statement.tableName = tableName } -func (statement *Statement) omitStr() string { - return statement.dialect.Quoter().Join(statement.OmitColumnMap, " ,") -} - // GenRawSQL generates correct raw sql func (statement *Statement) GenRawSQL() string { return statement.ReplaceQuote(statement.RawSQL) @@ -112,6 +109,7 @@ func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []int return statement.ReplaceQuote(condSQL), condArgs, nil } +// ReplaceQuote replace sql key words with quote func (statement *Statement) ReplaceQuote(sql string) string { if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL || statement.dialect.URI().DBType == schemas.SQLITE { @@ -591,7 +589,7 @@ func (statement *Statement) Having(conditions string) *Statement { return statement } -// Unscoped always disable struct tag "deleted" +// SetUnscoped always disable struct tag "deleted" func (statement *Statement) SetUnscoped() *Statement { statement.unscoped = true return statement @@ -923,10 +921,7 @@ func (statement *Statement) mergeConds(bean interface{}) error { statement.cond = statement.cond.And(autoCond) } - if err := statement.ProcessIDParam(); err != nil { - return err - } - return nil + return statement.ProcessIDParam() } // GenConds generates conditions diff --git a/internal/statements/statement_args.go b/internal/statements/statement_args.go index dc14467d..64089c1e 100644 --- a/internal/statements/statement_args.go +++ b/internal/statements/statement_args.go @@ -77,6 +77,7 @@ func convertArg(arg interface{}, convertFunc func(string) string) string { const insertSelectPlaceHolder = true +// WriteArg writes an arg func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case *builder.Builder: @@ -116,6 +117,7 @@ func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) er return nil } +// WriteArgs writes args func (statement *Statement) WriteArgs(w *builder.BytesWriter, args []interface{}) error { for i, arg := range args { if err := statement.WriteArg(w, arg); err != nil { diff --git a/names/mapper.go b/names/mapper.go index 79add76e..b0ce8076 100644 --- a/names/mapper.go +++ b/names/mapper.go @@ -16,6 +16,7 @@ type Mapper interface { Table2Obj(string) string } +// CacheMapper represents a cache mapper type CacheMapper struct { oriMapper Mapper obj2tableCache map[string]string @@ -24,12 +25,14 @@ type CacheMapper struct { table2objMutex sync.RWMutex } +// NewCacheMapper creates a cache mapper func NewCacheMapper(mapper Mapper) *CacheMapper { return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), table2objCache: make(map[string]string), } } +// Obj2Table implements Mapper func (m *CacheMapper) Obj2Table(o string) string { m.obj2tableMutex.RLock() t, ok := m.obj2tableCache[o] @@ -45,6 +48,7 @@ func (m *CacheMapper) Obj2Table(o string) string { return t } +// Table2Obj implements Mapper func (m *CacheMapper) Table2Obj(t string) string { m.table2objMutex.RLock() o, ok := m.table2objCache[t] @@ -60,15 +64,17 @@ func (m *CacheMapper) Table2Obj(t string) string { return o } -// SameMapper implements IMapper and provides same name between struct and +// SameMapper implements Mapper and provides same name between struct and // database table type SameMapper struct { } +// Obj2Table implements Mapper func (m SameMapper) Obj2Table(o string) string { return o } +// Table2Obj implements Mapper func (m SameMapper) Table2Obj(t string) string { return t } @@ -98,6 +104,7 @@ func snakeCasedName(name string) string { return b2s(newstr) } +// Obj2Table implements Mapper func (mapper SnakeMapper) Obj2Table(name string) string { return snakeCasedName(name) } @@ -127,6 +134,7 @@ func titleCasedName(name string) string { return b2s(newstr) } +// Table2Obj implements Mapper func (mapper SnakeMapper) Table2Obj(name string) string { return titleCasedName(name) } @@ -168,10 +176,12 @@ func gonicCasedName(name string) string { return strings.ToLower(string(newstr)) } +// Obj2Table implements Mapper func (mapper GonicMapper) Obj2Table(name string) string { return gonicCasedName(name) } +// Table2Obj implements Mapper func (mapper GonicMapper) Table2Obj(name string) string { newstr := make([]rune, 0) @@ -234,14 +244,17 @@ type PrefixMapper struct { Prefix string } +// Obj2Table implements Mapper func (mapper PrefixMapper) Obj2Table(name string) string { return mapper.Prefix + mapper.Mapper.Obj2Table(name) } +// Table2Obj implements Mapper func (mapper PrefixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) } +// NewPrefixMapper creates a prefix mapper func NewPrefixMapper(mapper Mapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } @@ -252,14 +265,17 @@ type SuffixMapper struct { Suffix string } +// Obj2Table implements Mapper func (mapper SuffixMapper) Obj2Table(name string) string { return mapper.Mapper.Obj2Table(name) + mapper.Suffix } +// Table2Obj implements Mapper func (mapper SuffixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } +// NewSuffixMapper creates a suffix mapper func NewSuffixMapper(mapper Mapper, suffix string) SuffixMapper { return SuffixMapper{mapper, suffix} } diff --git a/names/table_name.go b/names/table_name.go index 0afb1ae3..cc0e9274 100644 --- a/names/table_name.go +++ b/names/table_name.go @@ -19,6 +19,7 @@ var ( tvCache sync.Map ) +// GetTableName returns table name func GetTableName(mapper Mapper, v reflect.Value) string { if v.Type().Implements(tpTableName) { return v.Interface().(TableName).TableName() diff --git a/schemas/column.go b/schemas/column.go index 4f32afab..5808b84d 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -13,6 +13,7 @@ import ( "time" ) +// enumerates all database mapping way const ( TWOSIDES = iota + 1 ONLYTODB diff --git a/schemas/index.go b/schemas/index.go index 9541250f..8f31af52 100644 --- a/schemas/index.go +++ b/schemas/index.go @@ -28,6 +28,7 @@ func NewIndex(name string, indexType int) *Index { return &Index{true, name, indexType, make([]string, 0)} } +// XName returns the special index name for the table func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { @@ -43,11 +44,10 @@ func (index *Index) XName(tableName string) string { // AddColumn add columns which will be composite index func (index *Index) AddColumn(cols ...string) { - for _, col := range cols { - index.Cols = append(index.Cols, col) - } + index.Cols = append(index.Cols, cols...) } +// Equal return true if the two Index is equal func (index *Index) Equal(dst *Index) bool { if index.Type != dst.Type { return false diff --git a/schemas/pk.go b/schemas/pk.go index 03916b44..da3c7899 100644 --- a/schemas/pk.go +++ b/schemas/pk.go @@ -11,13 +11,16 @@ import ( "xorm.io/xorm/internal/utils" ) +// PK represents primary key values type PK []interface{} +// NewPK creates primay keys func NewPK(pks ...interface{}) *PK { p := PK(pks) return &p } +// IsZero return true if primay keys are zero func (p *PK) IsZero() bool { for _, k := range *p { if utils.IsZero(k) { @@ -27,6 +30,7 @@ func (p *PK) IsZero() bool { return false } +// ToString convert to SQL string func (p *PK) ToString() (string, error) { buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) @@ -34,6 +38,7 @@ func (p *PK) ToString() (string, error) { return buf.String(), err } +// FromString reads content to load primary keys func (p *PK) FromString(content string) error { dec := gob.NewDecoder(bytes.NewBufferString(content)) err := dec.Decode(p) diff --git a/schemas/quote.go b/schemas/quote.go index a0070048..71040ad9 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -16,10 +16,10 @@ type Quoter struct { } var ( - // AlwaysFalseReverse always think it's not a reverse word + // AlwaysNoReserve always think it's not a reverse word AlwaysNoReserve = func(string) bool { return false } - // AlwaysReverse always reverse the word + // AlwaysReserve always reverse the word AlwaysReserve = func(string) bool { return true } // CommanQuoteMark represnets the common quote mark @@ -29,10 +29,12 @@ var ( CommonQuoter = Quoter{CommanQuoteMark, CommanQuoteMark, AlwaysReserve} ) +// IsEmpty return true if no prefix and suffix func (q Quoter) IsEmpty() bool { return q.Prefix == 0 && q.Suffix == 0 } +// Quote quote a string func (q Quoter) Quote(s string) string { var buf strings.Builder q.QuoteTo(&buf, s) @@ -59,12 +61,14 @@ func (q Quoter) Trim(s string) string { return buf.String() } +// Join joins a slice with quoters func (q Quoter) Join(a []string, sep string) string { var b strings.Builder q.JoinWrite(&b, a, sep) return b.String() } +// JoinWrite writes quoted content to a builder func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error { if len(a) == 0 { return nil diff --git a/schemas/table.go b/schemas/table.go index 7ca9531f..bfa517aa 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -90,23 +90,28 @@ func (table *Table) PKColumns() []*Column { return columns } +// ColumnType returns a column's type func (table *Table) ColumnType(name string) reflect.Type { t, _ := table.Type.FieldByName(name) return t.Type } +// AutoIncrColumn returns autoincrement column func (table *Table) AutoIncrColumn() *Column { return table.GetColumn(table.AutoIncrement) } +// VersionColumn returns version column's information func (table *Table) VersionColumn() *Column { return table.GetColumn(table.Version) } +// UpdatedColumn returns updated column's information func (table *Table) UpdatedColumn() *Column { return table.GetColumn(table.Updated) } +// DeletedColumn returns deleted column's information func (table *Table) DeletedColumn() *Column { return table.GetColumn(table.Deleted) } diff --git a/schemas/type.go b/schemas/type.go index c6cdfb87..fc02f015 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -222,53 +222,55 @@ var ( // !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison var ( - c_EMPTY_STRING string - c_BOOL_DEFAULT bool - c_BYTE_DEFAULT byte - c_COMPLEX64_DEFAULT complex64 - c_COMPLEX128_DEFAULT complex128 - c_FLOAT32_DEFAULT float32 - c_FLOAT64_DEFAULT float64 - c_INT64_DEFAULT int64 - c_UINT64_DEFAULT uint64 - c_INT32_DEFAULT int32 - c_UINT32_DEFAULT uint32 - c_INT16_DEFAULT int16 - c_UINT16_DEFAULT uint16 - c_INT8_DEFAULT int8 - c_UINT8_DEFAULT uint8 - c_INT_DEFAULT int - c_UINT_DEFAULT uint - c_TIME_DEFAULT time.Time + emptyString string + boolDefault bool + byteDefault byte + complex64Default complex64 + complex128Default complex128 + float32Default float32 + float64Default float64 + int64Default int64 + uint64Default uint64 + int32Default int32 + uint32Default uint32 + int16Default int16 + uint16Default uint16 + int8Default int8 + uint8Default uint8 + intDefault int + uintDefault uint + timeDefault time.Time ) +// enumerates all types var ( - IntType = reflect.TypeOf(c_INT_DEFAULT) - Int8Type = reflect.TypeOf(c_INT8_DEFAULT) - Int16Type = reflect.TypeOf(c_INT16_DEFAULT) - Int32Type = reflect.TypeOf(c_INT32_DEFAULT) - Int64Type = reflect.TypeOf(c_INT64_DEFAULT) + IntType = reflect.TypeOf(intDefault) + Int8Type = reflect.TypeOf(int8Default) + Int16Type = reflect.TypeOf(int16Default) + Int32Type = reflect.TypeOf(int32Default) + Int64Type = reflect.TypeOf(int64Default) - UintType = reflect.TypeOf(c_UINT_DEFAULT) - Uint8Type = reflect.TypeOf(c_UINT8_DEFAULT) - Uint16Type = reflect.TypeOf(c_UINT16_DEFAULT) - Uint32Type = reflect.TypeOf(c_UINT32_DEFAULT) - Uint64Type = reflect.TypeOf(c_UINT64_DEFAULT) + UintType = reflect.TypeOf(uintDefault) + Uint8Type = reflect.TypeOf(uint8Default) + Uint16Type = reflect.TypeOf(uint16Default) + Uint32Type = reflect.TypeOf(uint32Default) + Uint64Type = reflect.TypeOf(uint64Default) - Float32Type = reflect.TypeOf(c_FLOAT32_DEFAULT) - Float64Type = reflect.TypeOf(c_FLOAT64_DEFAULT) + Float32Type = reflect.TypeOf(float32Default) + Float64Type = reflect.TypeOf(float64Default) - Complex64Type = reflect.TypeOf(c_COMPLEX64_DEFAULT) - Complex128Type = reflect.TypeOf(c_COMPLEX128_DEFAULT) + Complex64Type = reflect.TypeOf(complex64Default) + Complex128Type = reflect.TypeOf(complex128Default) - StringType = reflect.TypeOf(c_EMPTY_STRING) - BoolType = reflect.TypeOf(c_BOOL_DEFAULT) - ByteType = reflect.TypeOf(c_BYTE_DEFAULT) + StringType = reflect.TypeOf(emptyString) + BoolType = reflect.TypeOf(boolDefault) + ByteType = reflect.TypeOf(byteDefault) BytesType = reflect.SliceOf(ByteType) - TimeType = reflect.TypeOf(c_TIME_DEFAULT) + TimeType = reflect.TypeOf(timeDefault) ) +// enumerates all types var ( PtrIntType = reflect.PtrTo(IntType) PtrInt8Type = reflect.PtrTo(Int8Type) @@ -313,7 +315,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.Complex64, reflect.Complex128: st = SQLType{Varchar, 64, 0} case reflect.Array, reflect.Slice, reflect.Map: - if t.Elem() == reflect.TypeOf(c_BYTE_DEFAULT) { + if t.Elem() == reflect.TypeOf(byteDefault) { st = SQLType{Blob, 0, 0} } else { st = SQLType{Text, 0, 0} @@ -337,7 +339,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { return } -// default sql type change to go types +// SQLType2Type convert default sql type change to go types func SQLType2Type(st SQLType) reflect.Type { name := strings.ToUpper(st.Name) switch name { @@ -356,7 +358,7 @@ func SQLType2Type(st SQLType) reflect.Type { case Bool: return reflect.TypeOf(true) case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year: - return reflect.TypeOf(c_TIME_DEFAULT) + return reflect.TypeOf(timeDefault) case Decimal, Numeric, Money, SmallMoney: return reflect.TypeOf("") default: diff --git a/tags/parser.go b/tags/parser.go index 45dd6d9d..5ad67b53 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -21,9 +21,11 @@ import ( ) var ( + // ErrUnsupportedType represents an unsupported type error ErrUnsupportedType = errors.New("Unsupported type") ) +// Parser represents a parser for xorm tag type Parser struct { identifier string dialect dialects.Dialect @@ -34,6 +36,7 @@ type Parser struct { tableCache sync.Map // map[reflect.Type]*schemas.Table } +// NewParser creates a tag parser func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnMapper names.Mapper, cacherMgr *caches.Manager) *Parser { return &Parser{ identifier: identifier, @@ -45,29 +48,35 @@ func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnM } } +// GetTableMapper returns table mapper func (parser *Parser) GetTableMapper() names.Mapper { return parser.tableMapper } +// SetTableMapper sets table mapper func (parser *Parser) SetTableMapper(mapper names.Mapper) { parser.ClearCaches() parser.tableMapper = mapper } +// GetColumnMapper returns column mapper func (parser *Parser) GetColumnMapper() names.Mapper { return parser.columnMapper } +// SetColumnMapper sets column mapper func (parser *Parser) SetColumnMapper(mapper names.Mapper) { parser.ClearCaches() parser.columnMapper = mapper } +// SetIdentifier sets tag identifier func (parser *Parser) SetIdentifier(identifier string) { parser.ClearCaches() parser.identifier = identifier } +// ParseWithCache parse a struct with cache func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) { t := v.Type() tableI, ok := parser.tableCache.Load(t) From 912c2524f88d43fcd51827c23d105ed9bc573ac1 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 7 May 2021 09:19:03 +0800 Subject: [PATCH 029/179] RFC: Use provided type to create the dstTable rather than inferring from the SQL types (#1872) When using dumptables to convert between dialects if a struct is provided we should use it to generate the SQL types rather than infer them by mapping from the sql types. Signed-off-by: Andrew Thornton Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1872 Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- Makefile | 2 +- engine.go | 120 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 7305baa7..bf71b0f4 100644 --- a/Makefile +++ b/Makefile @@ -243,4 +243,4 @@ test-tidb\#%: go-check .PHONY: vet vet: - $(GO) vet $(shell $(GO) list ./...) \ No newline at end of file + $(GO) vet $(shell $(GO) list ./...) diff --git a/engine.go b/engine.go index 384928d6..0e498039 100644 --- a/engine.go +++ b/engine.go @@ -460,6 +460,11 @@ func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas. var v = fmt.Sprintf("%s", d) return "'" + strings.Replace(v, "'", "''", -1) + "'" } else if col.SQLType.IsTime() { + if dstDialect.URI().DBType == schemas.MSSQL && col.SQLType.Name == schemas.DateTime { + if t, ok := d.(time.Time); ok { + return "'" + t.UTC().Format("2006-01-02 15:04:05") + "'" + } + } var v = fmt.Sprintf("%s", d) if strings.HasSuffix(v, " +0000 UTC") { return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 UTC")]) @@ -537,6 +542,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } dstDialect.Init(&destURI) } + cacherMgr := caches.NewManager() + dstTableCache := tags.NewParser("xorm", dstDialect, engine.GetTableMapper(), engine.GetColumnMapper(), cacherMgr) _, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm %s, from %s to %s*/\n\n", time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.URI().DBType, dstDialect.URI().DBType)) @@ -545,9 +552,18 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } for i, table := range tables { - tableName := table.Name + dstTable := table + if table.Type != nil { + dstTable, err = dstTableCache.Parse(reflect.New(table.Type)) + if err != nil { + engine.logger.Errorf("Unable to infer table for %s in new dialect. Error: %v", table.Name) + dstTable = table + } + } + + dstTableName := dstTable.Name if dstDialect.URI().Schema != "" { - tableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, table.Name) + dstTableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, dstTable.Name) } originalTableName := table.Name if engine.dialect.URI().Schema != "" { @@ -559,27 +575,30 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } - sqls, _ := dstDialect.CreateTableSQL(table, tableName) + + sqls, _ := dstDialect.CreateTableSQL(dstTable, dstTableName) for _, s := range sqls { _, err = io.WriteString(w, s+";\n") if err != nil { return err } } - if len(table.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL { - fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", table.Name) + if len(dstTable.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL { + fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", dstTable.Name) } - for _, index := range table.Indexes { - _, err = io.WriteString(w, dstDialect.CreateIndexSQL(table.Name, index)+";\n") + for _, index := range dstTable.Indexes { + _, err = io.WriteString(w, dstDialect.CreateIndexSQL(dstTable.Name, index)+";\n") if err != nil { return err } } cols := table.ColumnsSeq() + dstCols := dstTable.ColumnsSeq() + colNames := engine.dialect.Quoter().Join(cols, ", ") - destColNames := dstDialect.Quoter().Join(cols, ", ") + destColNames := dstDialect.Quoter().Join(dstCols, ", ") rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(originalTableName)) if err != nil { @@ -587,35 +606,76 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } defer rows.Close() - for rows.Next() { - dest := make([]interface{}, len(cols)) - err = rows.ScanSlice(&dest) - if err != nil { - return err - } - - _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(tableName)+" ("+destColNames+") VALUES (") - if err != nil { - return err - } - - var temp string - for i, d := range dest { - col := table.GetColumn(cols[i]) - if col == nil { - return errors.New("unknow column error") + if table.Type != nil { + sess := engine.NewSession() + defer sess.Close() + for rows.Next() { + bean := reflect.New(table.Type) + fields, err := rows.Columns() + if err != nil { + return err + } + scanResults, err := sess.row2Slice(rows, fields, bean) + if err != nil { + return err + } + + dataStruct := utils.ReflectValue(bean.Interface()) + _, err = sess.slice2Bean(scanResults, fields, bean.Interface(), &dataStruct, table) + if err != nil { + return err + } + + _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") + if err != nil { + return err + } + + var temp string + for _, d := range dstCols { + col := table.GetColumn(d) + if col == nil { + return errors.New("unknown column error") + } + temp += "," + formatColumnValue(dstDialect, bean.Elem().FieldByName(col.FieldName).Interface(), col) + } + _, err = io.WriteString(w, temp[1:]+");\n") + if err != nil { + return err } - temp += "," + formatColumnValue(dstDialect, d, col) } - _, err = io.WriteString(w, temp[1:]+");\n") - if err != nil { - return err + } else { + for rows.Next() { + dest := make([]interface{}, len(cols)) + err = rows.ScanSlice(&dest) + if err != nil { + return err + } + + _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") + if err != nil { + return err + } + + var temp string + for i, d := range dest { + col := table.GetColumn(cols[i]) + if col == nil { + return errors.New("unknow column error") + } + + temp += "," + formatColumnValue(dstDialect, d, col) + } + _, err = io.WriteString(w, temp[1:]+");\n") + if err != nil { + return err + } } } // FIXME: Hack for postgres if dstDialect.URI().DBType == schemas.POSTGRES && table.AutoIncrColumn() != nil { - _, err = io.WriteString(w, "SELECT setval('"+tableName+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dstDialect.Quoter().Quote(tableName)+"), 1), false);\n") + _, err = io.WriteString(w, "SELECT setval('"+dstTableName+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dstDialect.Quoter().Quote(dstTableName)+"), 1), false);\n") if err != nil { return err } From 210c30a7ddec1ada4939af76aa9aa0b061cce68e Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 8 May 2021 12:27:22 +0800 Subject: [PATCH 030/179] Fix two issues with dumptables (#1903) There are two issues with #1872 which have become apparent after testing on Gitea. 1. Ensure structs which are have before processors run correctly 2. Ensure structs extending other structs work 3. Ensure that numerical enums become numeric Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1903 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- engine.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/engine.go b/engine.go index 0e498039..82593347 100644 --- a/engine.go +++ b/engine.go @@ -496,7 +496,7 @@ func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas. } return fmt.Sprintf("%v", strconv.FormatBool(v)) } - return fmt.Sprintf("%v", d) + return fmt.Sprintf("%d", d) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if col.SQLType.Name == schemas.Bool { v := reflect.ValueOf(d).Uint() > 0 @@ -508,7 +508,7 @@ func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas. } return fmt.Sprintf("%v", strconv.FormatBool(v)) } - return fmt.Sprintf("%v", d) + return fmt.Sprintf("%d", d) default: return fmt.Sprintf("%v", d) } @@ -554,7 +554,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch for i, table := range tables { dstTable := table if table.Type != nil { - dstTable, err = dstTableCache.Parse(reflect.New(table.Type)) + dstTable, err = dstTableCache.Parse(reflect.New(table.Type).Elem()) if err != nil { engine.logger.Errorf("Unable to infer table for %s in new dialect. Error: %v", table.Name) dstTable = table @@ -610,7 +610,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch sess := engine.NewSession() defer sess.Close() for rows.Next() { - bean := reflect.New(table.Type) + beanValue := reflect.New(table.Type) + bean := beanValue.Interface() fields, err := rows.Columns() if err != nil { return err @@ -620,8 +621,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } - dataStruct := utils.ReflectValue(bean.Interface()) - _, err = sess.slice2Bean(scanResults, fields, bean.Interface(), &dataStruct, table) + dataStruct := utils.ReflectValue(bean) + _, err = sess.slice2Bean(scanResults, fields, bean, &dataStruct, table) if err != nil { return err } @@ -637,7 +638,13 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch if col == nil { return errors.New("unknown column error") } - temp += "," + formatColumnValue(dstDialect, bean.Elem().FieldByName(col.FieldName).Interface(), col) + + fields := strings.Split(col.FieldName, ".") + field := dataStruct + for _, fieldName := range fields { + field = field.FieldByName(fieldName) + } + temp += "," + formatColumnValue(dstDialect, field.Interface(), col) } _, err = io.WriteString(w, temp[1:]+");\n") if err != nil { From dab09c73ab3b1b7a5dfd129146dd3922b6ea3a44 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 8 May 2021 21:39:03 +0800 Subject: [PATCH 031/179] Fix another bug with #1872 (#1905) Ensure that structs, arrays and slices are properly converted to strings. Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1905 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- engine.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/engine.go b/engine.go index 82593347..22ba6163 100644 --- a/engine.go +++ b/engine.go @@ -21,6 +21,7 @@ import ( "xorm.io/xorm/contexts" "xorm.io/xorm/core" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -457,7 +458,19 @@ func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas. } if col.SQLType.IsText() { - var v = fmt.Sprintf("%s", d) + var v string + switch reflect.TypeOf(d).Kind() { + case reflect.Struct, reflect.Array, reflect.Slice: + bytes, err := json.DefaultJSONHandler.Marshal(d) + if err != nil { + v = fmt.Sprintf("%s", d) + } else { + v = string(bytes) + } + default: + v = fmt.Sprintf("%s", d) + } + return "'" + strings.Replace(v, "'", "''", -1) + "'" } else if col.SQLType.IsTime() { if dstDialect.URI().DBType == schemas.MSSQL && col.SQLType.Name == schemas.DateTime { From d2f52eba64a3b2f89334e228905139fdd785e94a Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 9 May 2021 15:09:59 +0800 Subject: [PATCH 032/179] Byte strings in postgres aren't 0x... (#1906) Byte strings in postgres are actually E'\x...' not 0x... This is part of the follow-up to #1872 Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1906 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/postgres.go | 5 +++++ engine.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 2b234c66..e76e5b7e 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -824,6 +824,11 @@ func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { } } +// FormatBytes formats bytes +func (db *postgres) FormatBytes(bs []byte) string { + return fmt.Sprintf("E'\\x%x'", bs) +} + func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { diff --git a/engine.go b/engine.go index 22ba6163..d49eea9a 100644 --- a/engine.go +++ b/engine.go @@ -460,7 +460,7 @@ func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas. if col.SQLType.IsText() { var v string switch reflect.TypeOf(d).Kind() { - case reflect.Struct, reflect.Array, reflect.Slice: + case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map: bytes, err := json.DefaultJSONHandler.Marshal(d) if err != nil { v = fmt.Sprintf("%s", d) From 711f95d80414037332d602554fab5e89e7def536 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 14 May 2021 17:36:07 +0800 Subject: [PATCH 033/179] Update Changelog for v1.1.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac229c1..13e721ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.1.0](https://gitea.com/xorm/xorm/releases/tag/1.1.0) - 2021-05-14 + +* FEATURES + * Unsigned Support for mysql (#1889) + * Support modernc.org/sqlite (#1850) +* TESTING + * More tests (#1890) +* MISC + * Byte strings in postgres aren't 0x... (#1906) + * Fix another bug with #1872 (#1905) + * Fix two issues with dumptables (#1903) + * Fix comments (#1896) + * Fix comments (#1893) + * MariaDB 10.5 adds a suffix on old datatypes (#1885) + ## [1.0.7](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1336) - 2021-01-21 * BUGFIXES From d0219c37a8ca46e19bc8f86e3e36bea8eab4aeb8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Jun 2021 14:20:43 +0800 Subject: [PATCH 034/179] Support build flag jsoniter to replace default json (#1916) So you can `go build -tags=jsoniter` to use `github.com/json-iterator/go` as default json handler Fix #1744 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1916 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- go.mod | 1 + go.sum | 10 +++++++++- internal/json/jsoniter.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 internal/json/jsoniter.go diff --git a/go.mod b/go.mod index 5e073207..f6e4af90 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( github.com/denisenkom/go-mssqldb v0.9.0 github.com/go-sql-driver/mysql v1.5.0 + github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.7.0 github.com/mattn/go-sqlite3 v1.14.6 github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 230c16aa..3c79850c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -18,8 +19,11 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= @@ -28,6 +32,10 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go new file mode 100644 index 00000000..cfe7a19e --- /dev/null +++ b/internal/json/jsoniter.go @@ -0,0 +1,28 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build jsoniter + +package json + +import ( + jsoniter "github.com/json-iterator/go" +) + +func init() { + DefaultJSONHandler = JSONiter{} +} + +// JSONiter implements JSONInterface via jsoniter +type JSONiter struct{} + +// Marshal implements JSONInterface +func (JSONiter) Marshal(v interface{}) ([]byte, error) { + return jsoniter.Marshal(v) +} + +// Unmarshal implements JSONInterface +func (JSONiter) Unmarshal(data []byte, v interface{}) error { + return jsoniter.Unmarshal(data, v) +} From 136cf1a8432449b67a0d119f24f035e9a810bba7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Jun 2021 19:15:51 +0800 Subject: [PATCH 035/179] Add tests for FindAndCount with custom table name (#1918) Fix #1851 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1918 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_find_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index c3e99183..f90b5317 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -653,6 +653,31 @@ func TestFindAndCount2(t *testing.T) { assert.EqualValues(t, 0, cnt) } +type FindAndCountWithTableName struct { + Id int64 + Name string +} + +func (FindAndCountWithTableName) TableName() string { + return "find_and_count_with_table_name1" +} + +func TestFindAndCountWithTableName(t *testing.T) { + assert.NoError(t, PrepareEngine()) + assertSync(t, new(FindAndCountWithTableName)) + + cnt, err := testEngine.Insert(&FindAndCountWithTableName{ + Name: "1", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var res []FindAndCountWithTableName + cnt, err = testEngine.FindAndCount(&res) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) +} + type FindMapDevice struct { Deviceid string `xorm:"pk"` Status int From 409cb7c0f185e201f04c53024cefbc5f0eb177ca Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Jun 2021 20:21:39 +0800 Subject: [PATCH 036/179] Fix findandcount or count with groupby (#1915) Fix #1022, #1221, #1726, #1836, #1910. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1915 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_count_test.go | 172 ++++++++++++++++++ integrations/session_find_test.go | 30 +++ ...sion_stats_test.go => session_sum_test.go} | 141 -------------- internal/statements/query.go | 13 +- 4 files changed, 214 insertions(+), 142 deletions(-) create mode 100644 integrations/session_count_test.go rename integrations/{session_stats_test.go => session_sum_test.go} (53%) diff --git a/integrations/session_count_test.go b/integrations/session_count_test.go new file mode 100644 index 00000000..1517dede --- /dev/null +++ b/integrations/session_count_test.go @@ -0,0 +1,172 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "xorm.io/builder" +) + +func TestCount(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type UserinfoCount struct { + Departname string + } + assert.NoError(t, testEngine.Sync2(new(UserinfoCount))) + + colName := testEngine.GetColumnMapper().Obj2Table("Departname") + var cond builder.Cond = builder.Eq{ + "`" + colName + "`": "dev", + } + + total, err := testEngine.Where(cond).Count(new(UserinfoCount)) + assert.NoError(t, err) + assert.EqualValues(t, 0, total) + + cnt, err := testEngine.Insert(&UserinfoCount{ + Departname: "dev", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + total, err = testEngine.Where(cond).Count(new(UserinfoCount)) + assert.NoError(t, err) + assert.EqualValues(t, 1, total) + + total, err = testEngine.Where(cond).Table("userinfo_count").Count() + assert.NoError(t, err) + assert.EqualValues(t, 1, total) + + total, err = testEngine.Table("userinfo_count").Count() + assert.NoError(t, err) + assert.EqualValues(t, 1, total) +} + +func TestSQLCount(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type UserinfoCount2 struct { + Id int64 + Departname string + } + + type UserinfoBooks struct { + Id int64 + Pid int64 + IsOpen bool + } + + assertSync(t, new(UserinfoCount2), new(UserinfoBooks)) + + total, err := testEngine.SQL("SELECT count(id) FROM " + testEngine.TableName("userinfo_count2", true)). + Count() + assert.NoError(t, err) + assert.EqualValues(t, 0, total) +} + +func TestCountWithOthers(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type CountWithOthers struct { + Id int64 + Name string + } + + assertSync(t, new(CountWithOthers)) + + _, err := testEngine.Insert(&CountWithOthers{ + Name: "orderby", + }) + assert.NoError(t, err) + + _, err = testEngine.Insert(&CountWithOthers{ + Name: "limit", + }) + assert.NoError(t, err) + + total, err := testEngine.OrderBy("id desc").Limit(1).Count(new(CountWithOthers)) + assert.NoError(t, err) + assert.EqualValues(t, 2, total) +} + +type CountWithTableName struct { + Id int64 + Name string +} + +func (CountWithTableName) TableName() string { + return "count_with_table_name1" +} + +func TestWithTableName(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + assertSync(t, new(CountWithTableName)) + + _, err := testEngine.Insert(&CountWithTableName{ + Name: "orderby", + }) + assert.NoError(t, err) + + _, err = testEngine.Insert(CountWithTableName{ + Name: "limit", + }) + assert.NoError(t, err) + + total, err := testEngine.OrderBy("id desc").Count(new(CountWithTableName)) + assert.NoError(t, err) + assert.EqualValues(t, 2, total) + + total, err = testEngine.OrderBy("id desc").Count(CountWithTableName{}) + assert.NoError(t, err) + assert.EqualValues(t, 2, total) +} + +func TestCountWithSelectCols(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + assertSync(t, new(CountWithTableName)) + + _, err := testEngine.Insert(&CountWithTableName{ + Name: "orderby", + }) + assert.NoError(t, err) + + _, err = testEngine.Insert(CountWithTableName{ + Name: "limit", + }) + assert.NoError(t, err) + + total, err := testEngine.Cols("id").Count(new(CountWithTableName)) + assert.NoError(t, err) + assert.EqualValues(t, 2, total) + + total, err = testEngine.Select("count(id)").Count(CountWithTableName{}) + assert.NoError(t, err) + assert.EqualValues(t, 2, total) +} + +func TestCountWithGroupBy(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + assertSync(t, new(CountWithTableName)) + + _, err := testEngine.Insert(&CountWithTableName{ + Name: "1", + }) + assert.NoError(t, err) + + _, err = testEngine.Insert(CountWithTableName{ + Name: "2", + }) + assert.NoError(t, err) + + cnt, err := testEngine.GroupBy("name").Count(new(CountWithTableName)) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) +} diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index f90b5317..becb1494 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -678,6 +678,36 @@ func TestFindAndCountWithTableName(t *testing.T) { assert.EqualValues(t, 1, cnt) } +func TestFindAndCountWithGroupBy(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type FindAndCountWithGroupBy struct { + Id int64 + Age int `xorm:"index"` + Name string + } + + assert.NoError(t, testEngine.Sync2(new(FindAndCountWithGroupBy))) + + _, err := testEngine.Insert([]FindAndCountWithGroupBy{ + { + Name: "test1", + Age: 10, + }, + { + Name: "test2", + Age: 20, + }, + }) + assert.NoError(t, err) + + var results []FindAndCountWithGroupBy + cnt, err := testEngine.GroupBy("age").FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, 2, len(results)) +} + type FindMapDevice struct { Deviceid string `xorm:"pk"` Status int diff --git a/integrations/session_stats_test.go b/integrations/session_sum_test.go similarity index 53% rename from integrations/session_stats_test.go rename to integrations/session_sum_test.go index 47a64076..b447c699 100644 --- a/integrations/session_stats_test.go +++ b/integrations/session_sum_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/builder" ) func isFloatEq(i, j float64, precision int) bool { @@ -158,143 +157,3 @@ func TestSumCustomColumn(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 3, int(sumInt)) } - -func TestCount(t *testing.T) { - assert.NoError(t, PrepareEngine()) - - type UserinfoCount struct { - Departname string - } - assert.NoError(t, testEngine.Sync2(new(UserinfoCount))) - - colName := testEngine.GetColumnMapper().Obj2Table("Departname") - var cond builder.Cond = builder.Eq{ - "`" + colName + "`": "dev", - } - - total, err := testEngine.Where(cond).Count(new(UserinfoCount)) - assert.NoError(t, err) - assert.EqualValues(t, 0, total) - - cnt, err := testEngine.Insert(&UserinfoCount{ - Departname: "dev", - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - total, err = testEngine.Where(cond).Count(new(UserinfoCount)) - assert.NoError(t, err) - assert.EqualValues(t, 1, total) - - total, err = testEngine.Where(cond).Table("userinfo_count").Count() - assert.NoError(t, err) - assert.EqualValues(t, 1, total) - - total, err = testEngine.Table("userinfo_count").Count() - assert.NoError(t, err) - assert.EqualValues(t, 1, total) -} - -func TestSQLCount(t *testing.T) { - assert.NoError(t, PrepareEngine()) - - type UserinfoCount2 struct { - Id int64 - Departname string - } - - type UserinfoBooks struct { - Id int64 - Pid int64 - IsOpen bool - } - - assertSync(t, new(UserinfoCount2), new(UserinfoBooks)) - - total, err := testEngine.SQL("SELECT count(id) FROM " + testEngine.TableName("userinfo_count2", true)). - Count() - assert.NoError(t, err) - assert.EqualValues(t, 0, total) -} - -func TestCountWithOthers(t *testing.T) { - assert.NoError(t, PrepareEngine()) - - type CountWithOthers struct { - Id int64 - Name string - } - - assertSync(t, new(CountWithOthers)) - - _, err := testEngine.Insert(&CountWithOthers{ - Name: "orderby", - }) - assert.NoError(t, err) - - _, err = testEngine.Insert(&CountWithOthers{ - Name: "limit", - }) - assert.NoError(t, err) - - total, err := testEngine.OrderBy("id desc").Limit(1).Count(new(CountWithOthers)) - assert.NoError(t, err) - assert.EqualValues(t, 2, total) -} - -type CountWithTableName struct { - Id int64 - Name string -} - -func (CountWithTableName) TableName() string { - return "count_with_table_name1" -} - -func TestWithTableName(t *testing.T) { - assert.NoError(t, PrepareEngine()) - - assertSync(t, new(CountWithTableName)) - - _, err := testEngine.Insert(&CountWithTableName{ - Name: "orderby", - }) - assert.NoError(t, err) - - _, err = testEngine.Insert(CountWithTableName{ - Name: "limit", - }) - assert.NoError(t, err) - - total, err := testEngine.OrderBy("id desc").Count(new(CountWithTableName)) - assert.NoError(t, err) - assert.EqualValues(t, 2, total) - - total, err = testEngine.OrderBy("id desc").Count(CountWithTableName{}) - assert.NoError(t, err) - assert.EqualValues(t, 2, total) -} - -func TestCountWithSelectCols(t *testing.T) { - assert.NoError(t, PrepareEngine()) - - assertSync(t, new(CountWithTableName)) - - _, err := testEngine.Insert(&CountWithTableName{ - Name: "orderby", - }) - assert.NoError(t, err) - - _, err = testEngine.Insert(CountWithTableName{ - Name: "limit", - }) - assert.NoError(t, err) - - total, err := testEngine.Cols("id").Count(new(CountWithTableName)) - assert.NoError(t, err) - assert.EqualValues(t, 2, total) - - total, err = testEngine.Select("count(id)").Count(CountWithTableName{}) - assert.NoError(t, err) - assert.EqualValues(t, 2, total) -} diff --git a/internal/statements/query.go b/internal/statements/query.go index f1b36770..8b4cd919 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -181,11 +181,22 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa selectSQL = "count(*)" } } - sqlStr, condArgs, err := statement.genSelectSQL(selectSQL, false, false) + var subQuerySelect string + if statement.GroupByStr != "" { + subQuerySelect = statement.GroupByStr + } else { + subQuerySelect = selectSQL + } + + sqlStr, condArgs, err := statement.genSelectSQL(subQuerySelect, false, false) if err != nil { return "", nil, err } + if statement.GroupByStr != "" { + sqlStr = fmt.Sprintf("SELECT %s FROM (%s) sub", selectSQL, sqlStr) + } + return sqlStr, append(statement.joinArgs, condArgs...), nil } From 99cd8c80fbe42381abf747277532c66a2c819bb5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Jun 2021 20:34:45 +0800 Subject: [PATCH 037/179] Add tests for big.Int usage (#1917) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1917 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/types_test.go | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/integrations/types_test.go b/integrations/types_test.go index d0357d6b..4fc123ec 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -7,6 +7,8 @@ package integrations import ( "errors" "fmt" + "math/big" + "strconv" "testing" "xorm.io/xorm" @@ -402,3 +404,49 @@ func TestUnsigned(t *testing.T) { assert.False(t, true, "Unsigned is not implemented") } } + +type MyDecimal big.Int + +func (d *MyDecimal) FromDB(data []byte) error { + i, _ := strconv.ParseInt(string(data), 10, 64) + if d == nil { + d = (*MyDecimal)(big.NewInt(i)) + } else { + (*big.Int)(d).SetInt64(i) + } + return nil +} + +func (d *MyDecimal) ToDB() ([]byte, error) { + return []byte(fmt.Sprintf("%d", (*big.Int)(d).Int64())), nil +} + +func (d *MyDecimal) AsBigInt() *big.Int { + return (*big.Int)(d) +} + +func (d *MyDecimal) AsInt64() int64 { + return d.AsBigInt().Int64() +} + +func TestDecimal(t *testing.T) { + type MyMoney struct { + Id int64 + Account *MyDecimal + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(MyMoney)) + + _, err := testEngine.Insert(&MyMoney{ + Account: (*MyDecimal)(big.NewInt(10000000000000000)), + }) + assert.NoError(t, err) + + var m MyMoney + has, err := testEngine.Get(&m) + assert.NoError(t, err) + assert.True(t, has) + assert.NotNil(t, m.Account) + assert.EqualValues(t, 10000000000000000, m.Account.AsInt64()) +} From 8843a885039d719a657e4a2863c52f65fbcbda52 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sun, 6 Jun 2021 20:41:42 +0800 Subject: [PATCH 038/179] Fix typo in column.go (#1908) Avaiable -> Available Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1908 Reviewed-by: Lunny Xiao Co-authored-by: Ikko Ashimine Co-committed-by: Ikko Ashimine --- schemas/column.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/column.go b/schemas/column.go index 5808b84d..24b53802 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -24,7 +24,7 @@ const ( type Column struct { Name string TableName string - FieldName string // Avaiable only when parsed from a struct + FieldName string // Available only when parsed from a struct SQLType SQLType IsJSON bool Length int From 2910bffb1bb8fa345c6ad915b3cee642d0fdfced Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Jun 2021 21:31:15 +0800 Subject: [PATCH 039/179] Add test for find limit (#1904) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1904 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_find_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index becb1494..cd623417 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -84,6 +84,9 @@ func TestFind(t *testing.T) { err := testEngine.Find(&users) assert.NoError(t, err) + err = testEngine.Limit(10, 0).Find(&users) + assert.NoError(t, err) + users2 := make([]Userinfo, 0) var tbName = testEngine.Quote(testEngine.TableName(new(Userinfo), true)) err = testEngine.SQL("select * from " + tbName).Find(&users2) From b1449d0fb7b14ab94dc67e244ca48be2e9ea5eac Mon Sep 17 00:00:00 2001 From: janse_zyd Date: Sun, 6 Jun 2021 21:34:02 +0800 Subject: [PATCH 040/179] Replace goracle with godror (#1914) Co-authored-by: janse_zyd <59941594@qq.com> Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1914 Reviewed-by: Lunny Xiao Co-authored-by: janse_zyd Co-committed-by: janse_zyd --- dialects/dialect.go | 2 +- dialects/oracle.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index b02ec4ae..52655e6b 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -217,7 +217,7 @@ func regDrvsNDialects() bool { "sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, - "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, + "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, } for driverName, v := range providedDrvsNDialects { diff --git a/dialects/oracle.go b/dialects/oracle.go index 91eed251..72bbe54d 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -802,10 +802,10 @@ func (db *oracle) Filters() []Filter { } } -type goracleDriver struct { +type godrorDriver struct { } -func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*URI, error) { +func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) { db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] From 0f69f1eda2833d1b565209d7577a09cb95b76844 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 7 Jun 2021 13:45:29 +0800 Subject: [PATCH 041/179] Fix exist (#1921) Fix #1818 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1921 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_exist_test.go | 4 +++ internal/statements/query.go | 48 ++++++++++++++++-------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/integrations/session_exist_test.go b/integrations/session_exist_test.go index 6247c91a..29546376 100644 --- a/integrations/session_exist_test.go +++ b/integrations/session_exist_test.go @@ -75,6 +75,10 @@ func TestExistStruct(t *testing.T) { has, err = testEngine.Table("record_exist").Where("name = ?", "test2").Exist() assert.NoError(t, err) assert.False(t, has) + + has, err = testEngine.Table(new(RecordExist)).ID(1).Cols("id").Exist() + assert.NoError(t, err) + assert.True(t, has) } func TestExistStructForJoin(t *testing.T) { diff --git a/internal/statements/query.go b/internal/statements/query.go index 8b4cd919..e1091e9f 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -106,10 +106,13 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri // GenGetSQL generates Get SQL func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, error) { - v := rValue(bean) - isStruct := v.Kind() == reflect.Struct - if isStruct { - statement.SetRefBean(bean) + var isStruct bool + if bean != nil { + v := rValue(bean) + isStruct = v.Kind() == reflect.Struct + if isStruct { + statement.SetRefBean(bean) + } } var columnStr = statement.ColumnStr() @@ -340,12 +343,25 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac var args []interface{} var joinStr string var err error - if len(bean) == 0 { - tableName := statement.TableName() - if len(tableName) <= 0 { - return "", nil, ErrTableNotFound + var b interface{} = nil + if len(bean) > 0 { + b = bean[0] + beanValue := reflect.ValueOf(bean[0]) + if beanValue.Kind() != reflect.Ptr { + return "", nil, errors.New("needs a pointer") } + if beanValue.Elem().Kind() == reflect.Struct { + if err := statement.SetRefBean(bean[0]); err != nil { + return "", nil, err + } + } + } + tableName := statement.TableName() + if len(tableName) <= 0 { + return "", nil, ErrTableNotFound + } + if statement.RefTable == nil { tableName = statement.quote(tableName) if len(statement.JoinStr) > 0 { joinStr = statement.JoinStr @@ -376,22 +392,8 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac args = []interface{}{} } } else { - beanValue := reflect.ValueOf(bean[0]) - if beanValue.Kind() != reflect.Ptr { - return "", nil, errors.New("needs a pointer") - } - - if beanValue.Elem().Kind() == reflect.Struct { - if err := statement.SetRefBean(bean[0]); err != nil { - return "", nil, err - } - } - - if len(statement.TableName()) <= 0 { - return "", nil, ErrTableNotFound - } statement.Limit(1) - sqlStr, args, err = statement.GenGetSQL(bean[0]) + sqlStr, args, err = statement.GenGetSQL(b) if err != nil { return "", nil, err } From 43977186d339162eb9cade55d0e96cbae952a598 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 7 Jun 2021 17:36:22 +0800 Subject: [PATCH 042/179] Add tests for array store (#1922) Fix #504 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1922 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/types_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/integrations/types_test.go b/integrations/types_test.go index 4fc123ec..91f334d9 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -450,3 +450,38 @@ func TestDecimal(t *testing.T) { assert.NotNil(t, m.Account) assert.EqualValues(t, 10000000000000000, m.Account.AsInt64()) } + +type MyArray [20]byte + +func (d *MyArray) FromDB(data []byte) error { + for i, b := range data[:20] { + (*d)[i] = b + } + return nil +} + +func (d MyArray) ToDB() ([]byte, error) { + return d[:], nil +} + +func TestMyArray(t *testing.T) { + type MyArrayStruct struct { + Id int64 + Content MyArray + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(MyArrayStruct)) + + var v = [20]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + _, err := testEngine.Insert(&MyArrayStruct{ + Content: v, + }) + assert.NoError(t, err) + + var m MyArrayStruct + has, err := testEngine.Get(&m) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, v, m.Content) +} From eda6fefba3fb3580e4e69364efa9a09f8e533dc5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Jun 2021 21:41:13 +0800 Subject: [PATCH 043/179] Remove mymysql from ci (#1928) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1928 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/.drone.yml b/.drone.yml index 05f06e99..ac985f71 100644 --- a/.drone.yml +++ b/.drone.yml @@ -198,35 +198,11 @@ steps: - name: cache path: /go -- name: test-mymysql - pull: default - image: golang:1.15 - depends_on: - - test-mysql-utf8mb4 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' - TEST_MYSQL_HOST: mysql:3306 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mymysql - - TEST_CACHE_ENABLE=true make test-mymysql - - TEST_QUOTE_POLICY=reserved make test-mymysql - volumes: - - name: cache - path: /go - - name: rebuild-cache image: meltwater/drone-cache depends_on: - test-mysql - test-mysql-utf8mb4 - - test-mymysql pull: true settings: backend: "filesystem" From 7864cb5ae63acf39fc1686381dc4c82bd55366a9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 9 Jun 2021 09:05:50 +0800 Subject: [PATCH 044/179] refactor exprParam (#1825) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1825 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- internal/statements/expr.go | 93 ++++++++++++++++++++++ internal/statements/expr_param.go | 127 ------------------------------ internal/statements/insert.go | 12 +-- internal/statements/statement.go | 12 +-- session_update.go | 24 +++--- 5 files changed, 117 insertions(+), 151 deletions(-) create mode 100644 internal/statements/expr.go delete mode 100644 internal/statements/expr_param.go diff --git a/internal/statements/expr.go b/internal/statements/expr.go new file mode 100644 index 00000000..b44c96ca --- /dev/null +++ b/internal/statements/expr.go @@ -0,0 +1,93 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +// ErrUnsupportedExprType represents an error with unsupported express type +type ErrUnsupportedExprType struct { + tp string +} + +func (err ErrUnsupportedExprType) Error() string { + return fmt.Sprintf("Unsupported expression type: %v", err.tp) +} + +// Expr represents an SQL express +type Expr struct { + ColName string + Arg interface{} +} + +func (expr *Expr) WriteArgs(w *builder.BytesWriter) error { + switch arg := expr.Arg.(type) { + case *builder.Builder: + if _, err := w.WriteString("("); err != nil { + return err + } + if err := arg.WriteTo(w); err != nil { + return err + } + if _, err := w.WriteString(")"); err != nil { + return err + } + case string: + if arg == "" { + arg = "''" + } + if _, err := w.WriteString(fmt.Sprintf("%v", arg)); err != nil { + return err + } + default: + if _, err := w.WriteString("?"); err != nil { + return err + } + w.Append(arg) + } + return nil +} + +type exprParams []Expr + +func (exprs exprParams) ColNames() []string { + var cols = make([]string, 0, len(exprs)) + for _, expr := range exprs { + cols = append(cols, expr.ColName) + } + return cols +} + +func (exprs *exprParams) Add(name string, arg interface{}) { + *exprs = append(*exprs, Expr{name, arg}) +} + +func (exprs exprParams) IsColExist(colName string) bool { + for _, expr := range exprs { + if strings.EqualFold(schemas.CommonQuoter.Trim(expr.ColName), schemas.CommonQuoter.Trim(colName)) { + return true + } + } + return false +} + +func (exprs exprParams) WriteArgs(w *builder.BytesWriter) error { + for i, expr := range exprs { + if err := expr.WriteArgs(w); err != nil { + return err + } + if i != len(exprs)-1 { + if _, err := w.WriteString(","); err != nil { + return err + } + } + } + return nil +} diff --git a/internal/statements/expr_param.go b/internal/statements/expr_param.go deleted file mode 100644 index d0c355d3..00000000 --- a/internal/statements/expr_param.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2019 The Xorm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package statements - -import ( - "fmt" - "strings" - - "xorm.io/builder" - "xorm.io/xorm/schemas" -) - -// ErrUnsupportedExprType represents an error with unsupported express type -type ErrUnsupportedExprType struct { - tp string -} - -func (err ErrUnsupportedExprType) Error() string { - return fmt.Sprintf("Unsupported expression type: %v", err.tp) -} - -type exprParam struct { - colName string - arg interface{} -} - -type exprParams struct { - ColNames []string - Args []interface{} -} - -func (exprs *exprParams) Len() int { - return len(exprs.ColNames) -} - -func (exprs *exprParams) addParam(colName string, arg interface{}) { - exprs.ColNames = append(exprs.ColNames, colName) - exprs.Args = append(exprs.Args, arg) -} - -func (exprs *exprParams) IsColExist(colName string) bool { - for _, name := range exprs.ColNames { - if strings.EqualFold(schemas.CommonQuoter.Trim(name), schemas.CommonQuoter.Trim(colName)) { - return true - } - } - return false -} - -func (exprs *exprParams) getByName(colName string) (exprParam, bool) { - for i, name := range exprs.ColNames { - if strings.EqualFold(name, colName) { - return exprParam{name, exprs.Args[i]}, true - } - } - return exprParam{}, false -} - -func (exprs *exprParams) WriteArgs(w *builder.BytesWriter) error { - for i, expr := range exprs.Args { - switch arg := expr.(type) { - case *builder.Builder: - if _, err := w.WriteString("("); err != nil { - return err - } - if err := arg.WriteTo(w); err != nil { - return err - } - if _, err := w.WriteString(")"); err != nil { - return err - } - case string: - if arg == "" { - arg = "''" - } - if _, err := w.WriteString(fmt.Sprintf("%v", arg)); err != nil { - return err - } - default: - if _, err := w.WriteString("?"); err != nil { - return err - } - w.Append(arg) - } - if i != len(exprs.Args)-1 { - if _, err := w.WriteString(","); err != nil { - return err - } - } - } - return nil -} - -func (exprs *exprParams) writeNameArgs(w *builder.BytesWriter) error { - for i, colName := range exprs.ColNames { - if _, err := w.WriteString(colName); err != nil { - return err - } - if _, err := w.WriteString("="); err != nil { - return err - } - - switch arg := exprs.Args[i].(type) { - case *builder.Builder: - if _, err := w.WriteString("("); err != nil { - return err - } - if err := arg.WriteTo(w); err != nil { - return err - } - if _, err := w.WriteString("("); err != nil { - return err - } - default: - w.Append(exprs.Args[i]) - } - - if i+1 != len(exprs.ColNames) { - if _, err := w.WriteString(","); err != nil { - return err - } - } - } - return nil -} diff --git a/internal/statements/insert.go b/internal/statements/insert.go index 6cbbbeda..367dbdc9 100644 --- a/internal/statements/insert.go +++ b/internal/statements/insert.go @@ -59,7 +59,7 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } - if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames...), ","); err != nil { + if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames()...), ","); err != nil { return "", nil, err } @@ -79,7 +79,7 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } - if len(exprs.Args) > 0 { + if len(exprs) > 0 { if _, err := buf.WriteString(","); err != nil { return "", nil, err } @@ -112,7 +112,7 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } - if len(exprs.Args) > 0 { + if len(exprs) > 0 { if _, err := buf.WriteString(","); err != nil { return "", nil, err } @@ -152,7 +152,7 @@ func (statement *Statement) GenInsertMapSQL(columns []string, args []interface{} return "", nil, err } - if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(columns, exprs.ColNames...), ","); err != nil { + if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(columns, exprs.ColNames()...), ","); err != nil { return "", nil, err } @@ -166,7 +166,7 @@ func (statement *Statement) GenInsertMapSQL(columns []string, args []interface{} return "", nil, err } - if len(exprs.Args) > 0 { + if len(exprs) > 0 { if _, err := buf.WriteString(","); err != nil { return "", nil, err } @@ -190,7 +190,7 @@ func (statement *Statement) GenInsertMapSQL(columns []string, args []interface{} return "", nil, err } - if len(exprs.Args) > 0 { + if len(exprs) > 0 { if _, err := buf.WriteString(","); err != nil { return "", nil, err } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 3dd036a6..a52c6ca2 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -324,9 +324,9 @@ func (statement *Statement) TableName() string { // Incr Generate "Update ... Set column = column + arg" statement func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { if len(arg) > 0 { - statement.IncrColumns.addParam(column, arg[0]) + statement.IncrColumns.Add(column, arg[0]) } else { - statement.IncrColumns.addParam(column, 1) + statement.IncrColumns.Add(column, 1) } return statement } @@ -334,9 +334,9 @@ func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { // Decr Generate "Update ... Set column = column - arg" statement func (statement *Statement) Decr(column string, arg ...interface{}) *Statement { if len(arg) > 0 { - statement.DecrColumns.addParam(column, arg[0]) + statement.DecrColumns.Add(column, arg[0]) } else { - statement.DecrColumns.addParam(column, 1) + statement.DecrColumns.Add(column, 1) } return statement } @@ -344,9 +344,9 @@ func (statement *Statement) Decr(column string, arg ...interface{}) *Statement { // SetExpr Generate "Update ... Set column = {expression}" statement func (statement *Statement) SetExpr(column string, expression interface{}) *Statement { if e, ok := expression.(string); ok { - statement.ExprColumns.addParam(column, statement.dialect.Quoter().Replace(e)) + statement.ExprColumns.Add(column, statement.dialect.Quoter().Replace(e)) } else { - statement.ExprColumns.addParam(column, expression) + statement.ExprColumns.Add(column, expression) } return statement } diff --git a/session_update.go b/session_update.go index 0adac25e..9e4cddb1 100644 --- a/session_update.go +++ b/session_update.go @@ -224,35 +224,35 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 // for update action to like "column = column + ?" incColumns := session.statement.IncrColumns - for i, colName := range incColumns.ColNames { - colNames = append(colNames, session.engine.Quote(colName)+" = "+session.engine.Quote(colName)+" + ?") - args = append(args, incColumns.Args[i]) + for _, expr := range incColumns { + colNames = append(colNames, session.engine.Quote(expr.ColName)+" = "+session.engine.Quote(expr.ColName)+" + ?") + args = append(args, expr.Arg) } // for update action to like "column = column - ?" decColumns := session.statement.DecrColumns - for i, colName := range decColumns.ColNames { - colNames = append(colNames, session.engine.Quote(colName)+" = "+session.engine.Quote(colName)+" - ?") - args = append(args, decColumns.Args[i]) + for _, expr := range decColumns { + colNames = append(colNames, session.engine.Quote(expr.ColName)+" = "+session.engine.Quote(expr.ColName)+" - ?") + args = append(args, expr.Arg) } // for update action to like "column = expression" exprColumns := session.statement.ExprColumns - for i, colName := range exprColumns.ColNames { - switch tp := exprColumns.Args[i].(type) { + for _, expr := range exprColumns { + switch tp := expr.Arg.(type) { case string: if len(tp) == 0 { tp = "''" } - colNames = append(colNames, session.engine.Quote(colName)+"="+tp) + colNames = append(colNames, session.engine.Quote(expr.ColName)+"="+tp) case *builder.Builder: subQuery, subArgs, err := session.statement.GenCondSQL(tp) if err != nil { return 0, err } - colNames = append(colNames, session.engine.Quote(colName)+"=("+subQuery+")") + colNames = append(colNames, session.engine.Quote(expr.ColName)+"=("+subQuery+")") args = append(args, subArgs...) default: - colNames = append(colNames, session.engine.Quote(colName)+"=?") - args = append(args, exprColumns.Args[i]) + colNames = append(colNames, session.engine.Quote(expr.ColName)+"=?") + args = append(args, expr.Arg) } } From a6d2bfb4baa0f5319a648c7e26823018d8d239f1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 9 Jun 2021 14:07:34 +0800 Subject: [PATCH 045/179] Compitable with cockroach (#1930) Fix #1292 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1930 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/postgres.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index e76e5b7e..7a2cd87d 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1221,7 +1221,8 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN continue } indexName = strings.Trim(indexName, `" `) - if strings.HasSuffix(indexName, "_pkey") { + // ignore primary index + if strings.HasSuffix(indexName, "_pkey") || strings.EqualFold(indexName, "primary") { continue } if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") { @@ -1241,7 +1242,9 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN index := &schemas.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} for _, colName := range colNames { - index.Cols = append(index.Cols, strings.TrimSpace(strings.Replace(colName, `"`, "", -1))) + col := strings.TrimSpace(strings.Replace(colName, `"`, "", -1)) + fields := strings.Split(col, " ") + index.Cols = append(index.Cols, fields[0]) } index.IsRegular = isRegular indexes[index.Name] = index From fa4b4c4ee319c1d5fa5015de52e41e387ebb7e2c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 9 Jun 2021 19:30:15 +0800 Subject: [PATCH 046/179] Test for #1486 (#1942) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1942 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_insert_test.go | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 47789b8a..eaa1b2c7 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -986,3 +986,43 @@ func TestInsertTwice(t *testing.T) { assert.NoError(t, ssn.Commit()) } + +func TestInsertIntSlice(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type InsertIntSlice struct { + NameIDs []int `xorm:"json notnull"` + } + + assert.NoError(t, testEngine.Sync2(new(InsertIntSlice))) + + var v = InsertIntSlice{ + NameIDs: []int{1, 2}, + } + cnt, err := testEngine.Insert(&v) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var v2 InsertIntSlice + has, err := testEngine.Get(&v2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, v, v2) + + cnt, err = testEngine.Where("1=1").Delete(new(InsertIntSlice)) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var v3 = InsertIntSlice{ + NameIDs: nil, + } + cnt, err = testEngine.Insert(&v3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var v4 InsertIntSlice + has, err = testEngine.Get(&v4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, v3, v4) +} From 083ab38ea009033614b42212b52ae5eb71fd7076 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 10 Jun 2021 21:09:01 +0800 Subject: [PATCH 047/179] disable unnecessary clone (#1945) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1945 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 609 +++++++++++------------------------------------------ 1 file changed, 126 insertions(+), 483 deletions(-) diff --git a/.drone.yml b/.drone.yml index ac985f71..08be6a8d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,190 +1,65 @@ ---- -kind: pipeline -name: testing -steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go - -- name: test-vet - image: golang:1.15 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' - commands: - - make vet - - make fmt-check - volumes: - - name: cache - path: /go - when: - event: - - push - - pull_request - -- name: rebuild-cache - image: meltwater/drone-cache - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go - -volumes: - - name: cache - temp: {} - ---- -kind: pipeline -name: test-sqlite -depends_on: - - testing -steps: -- name: restore-cache - image: meltwater/drone-cache:dev - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go - -- name: test-sqlite3 - image: golang:1.15 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' - commands: - - make test-sqlite3 - - TEST_CACHE_ENABLE=true make test-sqlite3 - - TEST_QUOTE_POLICY=reserved make test-sqlite3 - volumes: - - name: cache - path: /go - -- name: test-sqlite - image: golang:1.15 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' - commands: - - make test-sqlite - - TEST_CACHE_ENABLE=true make test-sqlite - - TEST_QUOTE_POLICY=reserved make test-sqlite - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go - -volumes: - - name: cache - temp: {} - --- kind: pipeline name: test-mysql -depends_on: - - testing +environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache +- name: test-vet + image: golang:1.15 pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build volumes: - name: cache - path: /go - + path: /go/pkg/mod + commands: + - make vet +- name: test-sqlite3 + image: golang:1.15 + volumes: + - name: cache + path: /go/pkg/mod + depends_on: + - test-vet + commands: + - make fmt-check + - make test + - make test-sqlite3 + - TEST_CACHE_ENABLE=true make test-sqlite3 + - TEST_QUOTE_POLICY=reserved make test-sqlite3 + - make test-sqlite + - TEST_CACHE_ENABLE=true make test-sqlite + - TEST_QUOTE_POLICY=reserved make test-sqlite - name: test-mysql image: golang:1.15 + pull: never + volumes: + - name: cache + path: /go/pkg/mod + depends_on: + - test-vet environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql TEST_MYSQL_CHARSET: utf8 TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root TEST_MYSQL_PASSWORD: commands: - - make test - - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - - TEST_QUOTE_POLICY=reserved make test-mysql - volumes: - - name: cache - path: /go - name: test-mysql-utf8mb4 image: golang:1.15 + pull: never + volumes: + - name: cache + path: /go/pkg/mod depends_on: - test-mysql environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test @@ -192,38 +67,16 @@ steps: TEST_MYSQL_PASSWORD: commands: - make test-mysql - - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache - depends_on: - - test-mysql - - test-mysql-utf8mb4 - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: mysql - pull: default + pull: never image: mysql:5.7 environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes @@ -234,32 +87,18 @@ kind: pipeline name: test-mysql8 depends_on: - test-mysql - - test-sqlite +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go - - name: test-mysql8 image: golang:1.15 + pull: never + volumes: + - name: cache + path: /go/pkg/mod environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql8 TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test @@ -269,35 +108,15 @@ steps: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - pull: true - depends_on: - - test-mysql8 - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: mysql8 - pull: default + pull: never image: mysql:8.0 environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes @@ -308,31 +127,18 @@ kind: pipeline name: test-mariadb depends_on: - test-mysql8 +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go - - name: test-mariadb image: golang:1.15 + pull: never + volumes: + - name: cache + path: /go/pkg/mod environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mariadb TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test @@ -342,35 +148,15 @@ steps: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - depends_on: - - test-mariadb - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: mariadb - pull: default + pull: never image: mariadb:10.4 environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes @@ -381,32 +167,18 @@ kind: pipeline name: test-postgres depends_on: - test-mariadb +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build +- name: test-postgres + pull: never + image: golang:1.15 volumes: - name: cache - path: /go - -- name: test-postgres - pull: default - image: golang:1.15 + path: /go/pkg/mod environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_PGSQL_HOST: pgsql TEST_PGSQL_DBNAME: xorm_test TEST_PGSQL_USERNAME: postgres @@ -414,60 +186,32 @@ steps: commands: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres - - TEST_QUOTE_POLICY=reserved make test-postgres - volumes: - - name: cache - path: /go - name: test-postgres-schema - pull: default + pull: never image: golang:1.15 + volumes: + - name: cache + path: /go/pkg/mod depends_on: - test-postgres environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_PGSQL_HOST: pgsql TEST_PGSQL_SCHEMA: xorm TEST_PGSQL_DBNAME: xorm_test TEST_PGSQL_USERNAME: postgres TEST_PGSQL_PASSWORD: postgres commands: - - make test-postgres - - TEST_CACHE_ENABLE=true make test-postgres - TEST_QUOTE_POLICY=reserved make test-postgres - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - pull: true - depends_on: - - test-postgres-schema - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: pgsql - pull: default + pull: never image: postgres:9.5 environment: POSTGRES_DB: xorm_test @@ -479,32 +223,18 @@ kind: pipeline name: test-mssql depends_on: - test-postgres +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build +- name: test-mssql + pull: never + image: golang:1.15 volumes: - name: cache - path: /go - -- name: test-mssql - pull: default - image: golang:1.15 + path: /go/pkg/mod environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_MSSQL_HOST: mssql TEST_MSSQL_DBNAME: xorm_test TEST_MSSQL_USERNAME: sa @@ -514,33 +244,15 @@ steps: - TEST_CACHE_ENABLE=true make test-mssql - TEST_QUOTE_POLICY=reserved make test-mssql - TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: mssql - pull: default + pull: always image: microsoft/mssql-server-linux:latest environment: ACCEPT_EULA: Y @@ -552,32 +264,18 @@ kind: pipeline name: test-tidb depends_on: - test-mssql +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build +- name: test-tidb + pull: never + image: golang:1.15 volumes: - name: cache - path: /go - -- name: test-tidb - pull: default - image: golang:1.15 + path: /go/pkg/mod environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_TIDB_HOST: "tidb:4000" TEST_TIDB_DBNAME: xorm_test TEST_TIDB_USERNAME: root @@ -586,33 +284,15 @@ steps: - make test-tidb - TEST_CACHE_ENABLE=true make test-tidb - TEST_QUOTE_POLICY=reserved make test-tidb - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: tidb - pull: default + pull: never image: pingcap/tidb:v3.0.3 --- @@ -620,32 +300,18 @@ kind: pipeline name: test-cockroach depends_on: - test-tidb +trigger: + ref: + - refs/heads/master + - refs/pull/*/head steps: -- name: restore-cache - image: meltwater/drone-cache - pull: always - settings: - backend: "filesystem" - restore: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build +- name: test-cockroach + pull: never + image: golang:1.15 volumes: - name: cache - path: /go - -- name: test-cockroach - pull: default - image: golang:1.15 + path: /go/pkg/mod environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 - GOMODCACHE: '/drone/src/pkg.mod' - GOCACHE: '/drone/src/pkg.build' TEST_COCKROACH_HOST: "cockroach:26257" TEST_COCKROACH_DBNAME: xorm_test TEST_COCKROACH_USERNAME: root @@ -654,33 +320,15 @@ steps: - sleep 10 - make test-cockroach - TEST_CACHE_ENABLE=true make test-cockroach - volumes: - - name: cache - path: /go - -- name: rebuild-cache - image: meltwater/drone-cache:dev - pull: true - settings: - backend: "filesystem" - rebuild: true - cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' - archive_format: "gzip" - filesystem_cache_root: "/go" - mount: - - pkg.mod - - pkg.build - volumes: - - name: cache - path: /go volumes: - - name: cache - temp: {} +- name: cache + host: + path: /tmp/cache services: - name: cockroach - pull: default + pull: never image: cockroachdb/cockroach:v19.2.4 commands: - /cockroach/cockroach start --insecure @@ -689,8 +337,6 @@ services: kind: pipeline name: merge_coverage depends_on: - - testing - - test-sqlite - test-mysql - test-mysql8 - test-mariadb @@ -698,18 +344,15 @@ depends_on: - test-mssql - test-tidb - test-cockroach +trigger: + ref: + - refs/heads/master + - refs/pull/*/head +clone: + disable: true steps: - name: merge_coverage - pull: default + pull: never image: golang:1.15 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" commands: - make coverage - when: - branch: - - master - event: - - push - - pull_request From 5b624ed051af9ba47320ed5d763804cc46afb7a4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 10:47:31 +0800 Subject: [PATCH 048/179] Fix drone (#1946) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1946 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.drone.yml b/.drone.yml index 08be6a8d..9b4ffe9a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -76,7 +76,6 @@ volumes: services: - name: mysql - pull: never image: mysql:5.7 environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes @@ -116,7 +115,6 @@ volumes: services: - name: mysql8 - pull: never image: mysql:8.0 environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes @@ -156,7 +154,6 @@ volumes: services: - name: mariadb - pull: never image: mariadb:10.4 environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes @@ -211,7 +208,6 @@ volumes: services: - name: pgsql - pull: never image: postgres:9.5 environment: POSTGRES_DB: xorm_test @@ -292,7 +288,6 @@ volumes: services: - name: tidb - pull: never image: pingcap/tidb:v3.0.3 --- @@ -328,7 +323,6 @@ volumes: services: - name: cockroach - pull: never image: cockroachdb/cockroach:v19.2.4 commands: - /cockroach/cockroach start --insecure @@ -348,11 +342,8 @@ trigger: ref: - refs/heads/master - refs/pull/*/head -clone: - disable: true steps: - name: merge_coverage - pull: never image: golang:1.15 commands: - make coverage From 8e22bad3049838529397fdc07e3db905e4a9b8b9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 11:43:45 +0800 Subject: [PATCH 049/179] Fix bug didn't reset statement on update (#1939) Fix #1900 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1939 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_find_test.go | 26 ++++++++++++++++++++++++++ integrations/session_update_test.go | 4 ++-- session_update.go | 9 ++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index cd623417..0ea12e26 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "xorm.io/xorm" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" @@ -1020,3 +1021,28 @@ func TestDistinctAndCols(t *testing.T) { assert.EqualValues(t, 1, len(names)) assert.EqualValues(t, "test", names[0]) } + +func TestUpdateFind(t *testing.T) { + type TestUpdateFind struct { + Id int64 + Name string + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(TestUpdateFind)) + + session := testEngine.NewSession() + defer session.Close() + + var tuf = TestUpdateFind{ + Name: "test", + } + _, err := session.Insert(&tuf) + assert.NoError(t, err) + _, err = session.Where("id = ?", tuf.Id).Update(&TestUpdateFind{}) + assert.EqualError(t, xorm.ErrNoColumnsTobeUpdated, err.Error()) + + var tufs []TestUpdateFind + err = session.Where("id = ?", tuf.Id).Find(&tufs) + assert.NoError(t, err) +} diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index df0631c4..15d2f694 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -792,7 +792,7 @@ func TestNoUpdate(t *testing.T) { _, err = testEngine.ID(1).Update(&NoUpdate{}) assert.Error(t, err) - assert.EqualValues(t, "No content found to be updated", err.Error()) + assert.EqualError(t, xorm.ErrNoColumnsTobeUpdated, err.Error()) } func TestNewUpdate(t *testing.T) { @@ -922,7 +922,7 @@ func TestDeletedUpdate(t *testing.T) { assert.EqualValues(t, 1, cnt) cnt, err = testEngine.ID(s.Id).Cols("deleted_at").Update(&DeletedUpdatedStruct{}) - assert.EqualValues(t, "No content found to be updated", err.Error()) + assert.EqualError(t, xorm.ErrNoColumnsTobeUpdated, err.Error()) assert.EqualValues(t, 0, cnt) cnt, err = testEngine.ID(s.Id).Unscoped().Cols("deleted_at").Update(&DeletedUpdatedStruct{}) diff --git a/session_update.go b/session_update.go index 9e4cddb1..f791bb2d 100644 --- a/session_update.go +++ b/session_update.go @@ -17,6 +17,11 @@ import ( "xorm.io/xorm/schemas" ) +// enumerated all errors +var ( + ErrNoColumnsTobeUpdated = errors.New("no columns found to be updated") +) + func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { @@ -144,6 +149,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 defer session.Close() } + defer session.resetStatement() + if session.statement.LastError != nil { return 0, session.statement.LastError } @@ -329,7 +336,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } if len(colNames) <= 0 { - return 0, errors.New("No content found to be updated") + return 0, ErrNoColumnsTobeUpdated } condSQL, condArgs, err = session.statement.GenCondSQL(cond) From 2b78150f70af67582ade103c8123dae2d09e7cd0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 11:44:39 +0800 Subject: [PATCH 050/179] Fix create table with struct missing columns (#1938) Fix #1911, #1790 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1938 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_schema_test.go | 76 +++++++++++++++++++++++++++++ tags/parser.go | 18 +++++-- tags/parser_test.go | 11 ++--- 3 files changed, 92 insertions(+), 13 deletions(-) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 1ef653ef..fe9adb51 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -39,6 +39,82 @@ func TestCreateTable(t *testing.T) { assert.NoError(t, testEngine.Table("user_user").CreateTable(&UserinfoCreateTable{})) } +func TestCreateTable2(t *testing.T) { + type BaseModelLogicalDel struct { + Id string `xorm:"varchar(46) pk"` + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + DeletedAt *time.Time `xorm:"deleted"` + } + type TestPerson struct { + BaseModelLogicalDel `xorm:"extends"` + UserId string `xorm:"varchar(46) notnull"` + PersonId string `xorm:"varchar(46) notnull"` + Star bool + SortNo int + DispName string `xorm:"varchar(100)"` + FirstName string + LastName string + FirstNameKana string + LastNameKana string + BirthYear *int + BirthMonth *int + BirthDay *int + ImageId string `xorm:"varchar(46)"` + ImageDefaultId string `xorm:"varchar(46)"` + UserText string `xorm:"varchar(2000)"` + GenderId *int + At1 string `xorm:"varchar(10)"` + At1Rate int + At2 string `xorm:"varchar(10)"` + At2Rate int + At3 string `xorm:"varchar(10)"` + At3Rate int + At4 string `xorm:"varchar(10)"` + At4Rate int + At5 string `xorm:"varchar(10)"` + At5Rate int + At6 string `xorm:"varchar(10)"` + At6Rate int + } + + assert.NoError(t, PrepareEngine()) + + tb1, err := testEngine.TableInfo(TestPerson{}) + assert.NoError(t, err) + tb2, err := testEngine.TableInfo(new(TestPerson)) + assert.NoError(t, err) + cols1, cols2 := tb1.ColumnsSeq(), tb2.ColumnsSeq() + assert.EqualValues(t, len(cols1), len(cols2)) + for i, col := range cols1 { + assert.EqualValues(t, col, cols2[i]) + } + + result, err := testEngine.IsTableExist(new(TestPerson)) + assert.NoError(t, err) + if result { + assert.NoError(t, testEngine.DropTables(new(TestPerson))) + } + + assert.NoError(t, testEngine.CreateTables(new(TestPerson))) + tables1, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.Len(t, tables1, 1) + assert.EqualValues(t, len(cols1), len(tables1[0].Columns())) + + result, err = testEngine.IsTableExist(new(TestPerson)) + assert.NoError(t, err) + if result { + assert.NoError(t, testEngine.DropTables(new(TestPerson))) + } + + assert.NoError(t, testEngine.CreateTables(TestPerson{})) + tables2, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.Len(t, tables2, 1) + assert.EqualValues(t, len(cols1), len(tables2[0].Columns())) +} + func TestCreateMultiTables(t *testing.T) { assert.NoError(t, PrepareEngine()) diff --git a/tags/parser.go b/tags/parser.go index 5ad67b53..ff329daa 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -12,6 +12,7 @@ import ( "strings" "sync" "time" + "unicode" "xorm.io/xorm/caches" "xorm.io/xorm/convert" @@ -143,8 +144,18 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { var hasCacheTag, hasNoCacheTag bool for i := 0; i < t.NumField(); i++ { - tag := t.Field(i).Tag + var isUnexportField bool + for _, c := range t.Field(i).Name { + if unicode.IsLower(c) { + isUnexportField = true + } + break + } + if isUnexportField { + continue + } + tag := t.Field(i).Tag ormTagStr := tag.Get(parser.identifier) var col *schemas.Column fieldValue := v.Field(i) @@ -267,7 +278,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { addIndex(indexName, table, col, indexType) } } - } else if fieldValue.CanSet() { + } else { var sqlType schemas.SQLType if fieldValue.CanAddr() { if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { @@ -286,15 +297,12 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { idFieldColName = col.Name } - } else { - continue } if col.IsAutoIncrement { col.Nullable = false } table.AddColumn(col) - } // end for if idFieldColName != "" && len(table.PrimaryKeys) == 0 { diff --git a/tags/parser_test.go b/tags/parser_test.go index c3bf8051..5add1e13 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -53,7 +53,7 @@ func TestUnexportField(t *testing.T) { ) type VanilaStruct struct { - private int + private int // unexported fields will be ignored Public int } table, err := parser.Parse(reflect.ValueOf(new(VanilaStruct))) @@ -67,18 +67,13 @@ func TestUnexportField(t *testing.T) { } type TaggedStruct struct { - private int `xorm:"private"` + private int `xorm:"private"` // unexported fields will be ignored Public int `xorm:"-"` } table, err = parser.Parse(reflect.ValueOf(new(TaggedStruct))) assert.NoError(t, err) assert.EqualValues(t, "tagged_struct", table.Name) - assert.EqualValues(t, 1, len(table.Columns())) - - for _, col := range table.Columns() { - assert.EqualValues(t, "private", col.Name) - assert.NotEqual(t, "public", col.Name) - } + assert.EqualValues(t, 0, len(table.Columns())) } func TestParseWithOtherIdentifier(t *testing.T) { From 9c0901bd350289d6f190fdd5a92e5ed5f8fd77bd Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 12:11:30 +0800 Subject: [PATCH 051/179] Add sync tests to confirm #539 is gone (#1937) Fix #539 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1937 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_schema_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index fe9adb51..28c75119 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -474,6 +474,25 @@ func TestSync2_Default(t *testing.T) { assert.NoError(t, testEngine.Sync2(new(TestSync2Default))) } +func TestSync2_Default2(t *testing.T) { + type TestSync2Default2 struct { + Id int64 + UserId int64 `xorm:"default(1)"` + IsMember bool `xorm:"default(true)"` + Name string `xorm:"default('')"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(TestSync2Default2)) + assert.NoError(t, testEngine.Sync2(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync2(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync2(new(TestSync2Default2))) + + assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) +} + func TestModifyColum(t *testing.T) { // Since SQLITE don't support modify column SQL, currrently just ignore if testEngine.Dialect().URI().DBType == schemas.SQLITE { From 0907b7a7fd8fd3f9123ad857395a41254095826f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 13:27:56 +0800 Subject: [PATCH 052/179] test for unsigned int32 (#1923) To confirm #722 has been resolved. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1923 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/mssql.go | 4 +-- dialects/postgres.go | 4 +-- integrations/types_test.go | 59 +++++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index 15d1cd06..32e7ac50 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -284,7 +284,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case schemas.MediumInt, schemas.UnsignedInt: + case schemas.MediumInt: res = schemas.Int case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: res = db.defaultVarchar + "(MAX)" @@ -296,7 +296,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TinyInt: res = schemas.TinyInt c.Length = 0 - case schemas.BigInt, schemas.UnsignedBigInt: + case schemas.BigInt, schemas.UnsignedBigInt, schemas.UnsignedInt: res = schemas.BigInt c.Length = 0 case schemas.NVarchar: diff --git a/dialects/postgres.go b/dialects/postgres.go index 7a2cd87d..544c98e9 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -838,12 +838,12 @@ func (db *postgres) SQLType(c *schemas.Column) string { case schemas.Bit: res = schemas.Boolean return res - case schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedInt: + case schemas.MediumInt, schemas.Int, schemas.Integer: if c.IsAutoIncrement { return schemas.Serial } return schemas.Integer - case schemas.BigInt, schemas.UnsignedBigInt: + case schemas.BigInt, schemas.UnsignedBigInt, schemas.UnsignedInt: if c.IsAutoIncrement { return schemas.BigSerial } diff --git a/integrations/types_test.go b/integrations/types_test.go index 91f334d9..539171d5 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -7,6 +7,7 @@ package integrations import ( "errors" "fmt" + "math" "math/big" "strconv" "testing" @@ -378,7 +379,7 @@ func TestCustomType2(t *testing.T) { fmt.Println(users) } -func TestUnsigned(t *testing.T) { +func TestUnsignedUint64(t *testing.T) { type MyUnsignedStruct struct { Id uint64 } @@ -403,6 +404,62 @@ func TestUnsigned(t *testing.T) { default: assert.False(t, true, "Unsigned is not implemented") } + + // Only MYSQL database supports unsigned bigint + if testEngine.Dialect().URI().DBType != schemas.MYSQL { + return + } + + cnt, err := testEngine.Insert(&MyUnsignedStruct{ + Id: math.MaxUint64, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var v MyUnsignedStruct + has, err := testEngine.Get(&v) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, uint64(math.MaxUint64), v.Id) +} + +func TestUnsignedUint32(t *testing.T) { + type MyUnsignedInt32Struct struct { + Id uint32 + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(MyUnsignedInt32Struct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 1, len(tables[0].Columns())) + + switch testEngine.Dialect().URI().DBType { + case schemas.SQLITE: + assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) + case schemas.MYSQL: + assert.EqualValues(t, "UNSIGNED INT", tables[0].Columns()[0].SQLType.Name) + case schemas.POSTGRES: + assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) + case schemas.MSSQL: + assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) + default: + assert.False(t, true, "Unsigned is not implemented") + } + + cnt, err := testEngine.Insert(&MyUnsignedInt32Struct{ + Id: math.MaxUint32, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var v MyUnsignedInt32Struct + has, err := testEngine.Get(&v) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, uint64(math.MaxUint32), v.Id) } type MyDecimal big.Int From 7fd6356a85e3d29944201e09b0e1cb0f4ba8aec1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 15:06:05 +0800 Subject: [PATCH 053/179] Add DBVersion (#1723) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1723 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dialect.go | 1 + dialects/mssql.go | 25 +++++++++++++++++++++++++ dialects/mysql.go | 37 +++++++++++++++++++++++++++++++++++++ dialects/oracle.go | 20 ++++++++++++++++++++ dialects/postgres.go | 36 ++++++++++++++++++++++++++++++++++++ dialects/sqlite3.go | 21 +++++++++++++++++++++ engine.go | 12 +++--------- integrations/engine_test.go | 9 +++++++++ interface.go | 1 + schemas/version.go | 12 ++++++++++++ 10 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 schemas/version.go diff --git a/dialects/dialect.go b/dialects/dialect.go index 52655e6b..325836b4 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -44,6 +44,7 @@ type Dialect interface { URI() *URI SQLType(*schemas.Column) string FormatBytes(b []byte) string + Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) IsReserved(string) bool Quoter() schemas.Quoter diff --git a/dialects/mssql.go b/dialects/mssql.go index 32e7ac50..7e922e62 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -253,6 +253,31 @@ func (db *mssql) SetParams(params map[string]string) { } } +func (db *mssql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, + "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel') AS ProductLevel, SERVERPROPERTY ('edition') AS ProductEdition") + if err != nil { + return nil, err + } + defer rows.Close() + + var version, level, edition string + if !rows.Next() { + return nil, errors.New("unknow version") + } + + if err := rows.Scan(&version, &level, &edition); err != nil { + return nil, err + } + + // MSSQL: Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64) Nov 30 2018 12:57:58 Copyright (C) 2017 Microsoft Corporation Developer Edition (64-bit) on Linux (Ubuntu 16.04.5 LTS) + return &schemas.Version{ + Number: version, + Level: level, + Edition: edition, + }, nil +} + func (db *mssql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { diff --git a/dialects/mysql.go b/dialects/mysql.go index 2b530daf..a169b901 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -188,6 +188,43 @@ func (db *mysql) Init(uri *URI) error { return db.Base.Init(db, uri) } +func (db *mysql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "SELECT @@VERSION") + if err != nil { + return nil, err + } + defer rows.Close() + + var version string + if !rows.Next() { + return nil, errors.New("Unknow version") + } + + if err := rows.Scan(&version); err != nil { + return nil, err + } + + fields := strings.Split(version, "-") + if len(fields) == 3 && fields[1] == "TiDB" { + // 5.7.25-TiDB-v3.0.3 + return &schemas.Version{ + Number: strings.TrimPrefix(fields[2], "v"), + Level: fields[0], + Edition: fields[1], + }, nil + } + + var edition string + if len(fields) == 2 { + edition = fields[1] + } + + return &schemas.Version{ + Number: fields[0], + Edition: edition, + }, nil +} + func (db *mysql) SetParams(params map[string]string) { rowFormat, ok := params["rowFormat"] if ok { diff --git a/dialects/oracle.go b/dialects/oracle.go index 72bbe54d..0b06c4c6 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -515,6 +515,26 @@ func (db *oracle) Init(uri *URI) error { return db.Base.Init(db, uri) } +func (db *oracle) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "select * from v$version where banner like 'Oracle%'") + if err != nil { + return nil, err + } + defer rows.Close() + + var version string + if !rows.Next() { + return nil, errors.New("unknow version") + } + + if err := rows.Scan(&version); err != nil { + return nil, err + } + return &schemas.Version{ + Number: version, + }, nil +} + func (db *oracle) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { diff --git a/dialects/postgres.go b/dialects/postgres.go index 544c98e9..52c88567 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -788,6 +788,42 @@ func (db *postgres) Init(uri *URI) error { return db.Base.Init(db, uri) } +func (db *postgres) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "SELECT version()") + if err != nil { + return nil, err + } + defer rows.Close() + + var version string + if !rows.Next() { + return nil, errors.New("Unknow version") + } + + if err := rows.Scan(&version); err != nil { + return nil, err + } + + // Postgres: 9.5.22 on x86_64-pc-linux-gnu (Debian 9.5.22-1.pgdg90+1), compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit + // CockroachDB CCL v19.2.4 (x86_64-unknown-linux-gnu, built + if strings.HasPrefix(version, "CockroachDB") { + versions := strings.Split(strings.TrimPrefix(version, "CockroachDB CCL "), " ") + return &schemas.Version{ + Number: strings.TrimPrefix(versions[0], "v"), + Edition: "CockroachDB", + }, nil + } else if strings.HasPrefix(version, "PostgreSQL") { + versions := strings.Split(strings.TrimPrefix(version, "PostgreSQL "), " on ") + return &schemas.Version{ + Number: versions[0], + Level: versions[1], + Edition: "PostgreSQL", + }, nil + } + + return nil, errors.New("unknow database version") +} + func (db *postgres) getSchema() string { if db.uri.Schema != "" { return db.uri.Schema diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 82683606..a42aad48 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -160,6 +160,27 @@ func (db *sqlite3) Init(uri *URI) error { return db.Base.Init(db, uri) } +func (db *sqlite3) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "SELECT sqlite_version()") + if err != nil { + return nil, err + } + defer rows.Close() + + var version string + if !rows.Next() { + return nil, errors.New("Unknow version") + } + + if err := rows.Scan(&version); err != nil { + return nil, err + } + return &schemas.Version{ + Number: version, + Edition: "sqlite", + }, nil +} + func (db *sqlite3) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: diff --git a/engine.go b/engine.go index d49eea9a..649ec1a2 100644 --- a/engine.go +++ b/engine.go @@ -925,15 +925,9 @@ func (engine *Engine) Having(conditions string) *Session { return session.Having(conditions) } -// Table table struct -type Table struct { - *schemas.Table - Name string -} - -// IsValid if table is valid -func (t *Table) IsValid() bool { - return t.Table != nil && len(t.Name) > 0 +// DBVersion returns the database version +func (engine *Engine) DBVersion() (*schemas.Version, error) { + return engine.dialect.Version(engine.defaultContext, engine.db) } // TableInfo get table info according to bean's content diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 3b843f16..9b70f9b5 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -200,3 +200,12 @@ func TestImport(t *testing.T) { assert.NoError(t, err) assert.NoError(t, sess.Commit()) } + +func TestDBVersion(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + version, err := testEngine.DBVersion() + assert.NoError(t, err) + + fmt.Println(testEngine.Dialect().URI().DBType, "version is", version) +} diff --git a/interface.go b/interface.go index 55162c8c..d31323ff 100644 --- a/interface.go +++ b/interface.go @@ -83,6 +83,7 @@ type EngineInterface interface { Context(context.Context) *Session CreateTables(...interface{}) error DBMetas() ([]*schemas.Table, error) + DBVersion() (*schemas.Version, error) Dialect() dialects.Dialect DriverName() string DropTables(...interface{}) error diff --git a/schemas/version.go b/schemas/version.go new file mode 100644 index 00000000..ba789679 --- /dev/null +++ b/schemas/version.go @@ -0,0 +1,12 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +// Version represents a database version +type Version struct { + Number string // the version number which could be compared + Level string + Edition string +} From 00ee06fdd5b83e68eff442a2e2a211a8bc68b9f2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 20:27:49 +0800 Subject: [PATCH 054/179] Add test for dump table with default value (#1950) Confirm #1391 resolved. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1950 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/engine_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 9b70f9b5..344e95a8 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -176,6 +176,23 @@ func TestDumpTables(t *testing.T) { } } +func TestDumpTables2(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type TestDumpTableStruct2 struct { + Id int64 + Created time.Time `xorm:"Default CURRENT_TIMESTAMP"` + } + + assertSync(t, new(TestDumpTableStruct2)) + + fp := fmt.Sprintf("./dump2-%v-table.sql", testEngine.Dialect().URI().DBType) + os.Remove(fp) + tb, err := testEngine.TableInfo(new(TestDumpTableStruct2)) + assert.NoError(t, err) + assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, fp)) +} + func TestSetSchema(t *testing.T) { assert.NoError(t, PrepareEngine()) From e1422f183c89cb984b11db108911c170c2ed4ffb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Jun 2021 20:35:22 +0800 Subject: [PATCH 055/179] Add test to confirm #1247 resolved (#1951) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1951 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_update_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 15d2f694..796bfa0a 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -472,6 +472,11 @@ func TestUpdateIncrDecr(t *testing.T) { cnt, err = testEngine.ID(col1.Id).Cols(colName).Incr(colName).Update(col1) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + + testEngine.SetColumnMapper(testEngine.GetColumnMapper()) + cnt, err = testEngine.Cols(colName).Decr(colName, 2).ID(col1.Id).Update(new(UpdateIncr)) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } type UpdatedUpdate struct { From 94614619671583f31a1ab6e1710723cd0ac1ae0d Mon Sep 17 00:00:00 2001 From: knice88 Date: Sat, 12 Jun 2021 22:47:15 +0800 Subject: [PATCH 056/179] fix pg GetColumns missing comment (#1949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xorm reverse生成的结构体缺少备注信息 Co-authored-by: chad Reviewed-on: https://gitea.com/xorm/xorm/pulls/1949 Reviewed-by: Lunny Xiao Co-authored-by: knice88 Co-committed-by: knice88 --- dialects/postgres.go | 11 ++++++++--- integrations/engine_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 52c88567..9acf763a 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1044,12 +1044,13 @@ func (db *postgres) IsColumnExist(queryer core.Queryer, ctx context.Context, tab func (db *postgres) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} - s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, + s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, description, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey FROM pg_attribute f JOIN pg_class c ON c.oid = f.attrelid JOIN pg_type t ON t.oid = f.atttypid LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum + LEFT JOIN pg_description de ON f.attrelid=de.objoid AND f.attnum=de.objsubid LEFT JOIN pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey) LEFT JOIN pg_class AS g ON p.confrelid = g.oid @@ -1078,9 +1079,9 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A col.Indexes = make(map[string]int) var colName, isNullable, dataType string - var maxLenStr, colDefault *string + var maxLenStr, colDefault, description *string var isPK, isUnique bool - err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &isPK, &isUnique) + err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &description, &isPK, &isUnique) if err != nil { return nil, nil, err } @@ -1126,6 +1127,10 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A col.DefaultIsEmpty = true } + if description != nil { + col.Comment = *description + } + if isPK { col.IsPrimaryKey = true } diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 344e95a8..a06d91aa 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -226,3 +226,39 @@ func TestDBVersion(t *testing.T) { fmt.Println(testEngine.Dialect().URI().DBType, "version is", version) } + +func TestGetColumns(t *testing.T) { + if testEngine.Dialect().URI().DBType != schemas.POSTGRES { + t.Skip() + return + } + type TestCommentStruct struct { + HasComment int + NoComment int + } + + assertSync(t, new(TestCommentStruct)) + + comment := "this is a comment" + sql := fmt.Sprintf("comment on column %s.%s is '%s'", testEngine.TableName(new(TestCommentStruct), true), "has_comment", comment) + _, err := testEngine.Exec(sql) + assert.NoError(t, err) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + tableName := testEngine.GetColumnMapper().Obj2Table("TestCommentStruct") + var hasComment, noComment string + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("has_comment") + assert.NotNil(t, col) + hasComment = col.Comment + col2 := table.GetColumn("no_comment") + assert.NotNil(t, col2) + noComment = col2.Comment + break + } + } + assert.Equal(t, comment, hasComment) + assert.Zero(t, noComment) +} From bc25b4128bc7396d34d1414e6355bec7d1d15a52 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 14 Jun 2021 11:23:05 +0800 Subject: [PATCH 057/179] Fix #1663 (#1952) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1952 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- internal/statements/statement.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index a52c6ca2..ca59817b 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -208,20 +208,18 @@ func (statement *Statement) quote(s string) string { // And add Where & and statement func (statement *Statement) And(query interface{}, args ...interface{}) *Statement { - switch query.(type) { + switch qr := query.(type) { case string: - cond := builder.Expr(query.(string), args...) + cond := builder.Expr(qr, args...) statement.cond = statement.cond.And(cond) case map[string]interface{}: - queryMap := query.(map[string]interface{}) - newMap := make(map[string]interface{}) - for k, v := range queryMap { - newMap[statement.quote(k)] = v + cond := make(builder.Eq) + for k, v := range qr { + cond[statement.quote(k)] = v } - statement.cond = statement.cond.And(builder.Eq(newMap)) - case builder.Cond: - cond := query.(builder.Cond) statement.cond = statement.cond.And(cond) + case builder.Cond: + statement.cond = statement.cond.And(qr) for _, v := range args { if vv, ok := v.(builder.Cond); ok { statement.cond = statement.cond.And(vv) @@ -236,23 +234,25 @@ func (statement *Statement) And(query interface{}, args ...interface{}) *Stateme // Or add Where & Or statement func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement { - switch query.(type) { + switch qr := query.(type) { case string: - cond := builder.Expr(query.(string), args...) + cond := builder.Expr(qr, args...) statement.cond = statement.cond.Or(cond) case map[string]interface{}: - cond := builder.Eq(query.(map[string]interface{})) + cond := make(builder.Eq) + for k, v := range qr { + cond[statement.quote(k)] = v + } statement.cond = statement.cond.Or(cond) case builder.Cond: - cond := query.(builder.Cond) - statement.cond = statement.cond.Or(cond) + statement.cond = statement.cond.Or(qr) for _, v := range args { if vv, ok := v.(builder.Cond); ok { statement.cond = statement.cond.Or(vv) } } default: - // TODO: not support condition type + statement.LastError = ErrConditionType } return statement } From 5a58a272bc86d9281a1766f5681df49d67526bee Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 15 Jun 2021 20:28:49 +0800 Subject: [PATCH 058/179] fix lint (#1953) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1953 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .revive.toml | 14 ++++++++------ convert.go | 4 ---- integrations/session_insert_test.go | 2 -- internal/statements/expr.go | 1 + internal/statements/query.go | 2 +- internal/statements/statement_test.go | 6 ------ log/logger.go | 9 --------- schemas/table_test.go | 7 ------- session_update.go | 1 - 9 files changed, 10 insertions(+), 36 deletions(-) diff --git a/.revive.toml b/.revive.toml index 6dec7465..9e3b629d 100644 --- a/.revive.toml +++ b/.revive.toml @@ -8,20 +8,22 @@ warningCode = 1 [rule.context-as-argument] [rule.context-keys-type] [rule.dot-imports] +[rule.empty-lines] +[rule.errorf] [rule.error-return] [rule.error-strings] [rule.error-naming] [rule.exported] [rule.if-return] [rule.increment-decrement] -[rule.var-naming] - arguments = [["ID", "UID", "UUID", "URL", "JSON"], []] -[rule.var-declaration] +[rule.indent-error-flow] [rule.package-comments] [rule.range] [rule.receiver-naming] +[rule.struct-tag] [rule.time-naming] [rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] -[rule.struct-tag] \ No newline at end of file +[rule.unnecessary-stmt] +[rule.var-declaration] +[rule.var-naming] + arguments = [["ID", "UID", "UUID", "URL", "JSON"], []] \ No newline at end of file diff --git a/convert.go b/convert.go index c19d30e0..ee5b6029 100644 --- a/convert.go +++ b/convert.go @@ -416,7 +416,3 @@ func int64ToIntValue(id int64, tp reflect.Type) reflect.Value { } return reflect.ValueOf(v).Elem().Convert(tp) } - -func int64ToInt(id int64, tp reflect.Type) interface{} { - return int64ToIntValue(id, tp).Interface() -} diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index eaa1b2c7..e5d880ae 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -32,7 +32,6 @@ func TestInsertOne(t *testing.T) { } func TestInsertMulti(t *testing.T) { - assert.NoError(t, PrepareEngine()) type TestMulti struct { Id int64 `xorm:"int(11) pk"` @@ -78,7 +77,6 @@ func insertMultiDatas(step int, datas interface{}) (num int64, err error) { } func callbackLooper(datas interface{}, step int, actionFunc func(interface{}) error) (err error) { - sliceValue := reflect.Indirect(reflect.ValueOf(datas)) if sliceValue.Kind() != reflect.Slice { return fmt.Errorf("not slice") diff --git a/internal/statements/expr.go b/internal/statements/expr.go index b44c96ca..c2a2e1cc 100644 --- a/internal/statements/expr.go +++ b/internal/statements/expr.go @@ -27,6 +27,7 @@ type Expr struct { Arg interface{} } +// WriteArgs writes args to the writer func (expr *Expr) WriteArgs(w *builder.BytesWriter) error { switch arg := expr.Arg.(type) { case *builder.Builder: diff --git a/internal/statements/query.go b/internal/statements/query.go index e1091e9f..a972a8e0 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -343,7 +343,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac var args []interface{} var joinStr string var err error - var b interface{} = nil + var b interface{} if len(bean) > 0 { b = bean[0] beanValue := reflect.ValueOf(bean[0]) diff --git a/internal/statements/statement_test.go b/internal/statements/statement_test.go index 15f446f4..ba92330e 100644 --- a/internal/statements/statement_test.go +++ b/internal/statements/statement_test.go @@ -78,7 +78,6 @@ func TestColumnsStringGeneration(t *testing.T) { } func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { - b.StopTimer() mapCols := make(map[string]bool) @@ -101,9 +100,7 @@ func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - for _, col := range cols { - if _, ok := getFlagForColumn(mapCols, col); !ok { b.Fatal("Unexpected result") } @@ -112,7 +109,6 @@ func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { } func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { - b.StopTimer() mapCols := make(map[string]bool) @@ -131,9 +127,7 @@ func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - for _, col := range cols { - if _, ok := getFlagForColumn(mapCols, col); ok { b.Fatal("Unexpected result") } diff --git a/log/logger.go b/log/logger.go index eeb63693..3b6db34e 100644 --- a/log/logger.go +++ b/log/logger.go @@ -132,7 +132,6 @@ func (s *SimpleLogger) Error(v ...interface{}) { if s.level <= LOG_ERR { s.ERR.Output(2, fmt.Sprintln(v...)) } - return } // Errorf implement ILogger @@ -140,7 +139,6 @@ func (s *SimpleLogger) Errorf(format string, v ...interface{}) { if s.level <= LOG_ERR { s.ERR.Output(2, fmt.Sprintf(format, v...)) } - return } // Debug implement ILogger @@ -148,7 +146,6 @@ func (s *SimpleLogger) Debug(v ...interface{}) { if s.level <= LOG_DEBUG { s.DEBUG.Output(2, fmt.Sprintln(v...)) } - return } // Debugf implement ILogger @@ -156,7 +153,6 @@ func (s *SimpleLogger) Debugf(format string, v ...interface{}) { if s.level <= LOG_DEBUG { s.DEBUG.Output(2, fmt.Sprintf(format, v...)) } - return } // Info implement ILogger @@ -164,7 +160,6 @@ func (s *SimpleLogger) Info(v ...interface{}) { if s.level <= LOG_INFO { s.INFO.Output(2, fmt.Sprintln(v...)) } - return } // Infof implement ILogger @@ -172,7 +167,6 @@ func (s *SimpleLogger) Infof(format string, v ...interface{}) { if s.level <= LOG_INFO { s.INFO.Output(2, fmt.Sprintf(format, v...)) } - return } // Warn implement ILogger @@ -180,7 +174,6 @@ func (s *SimpleLogger) Warn(v ...interface{}) { if s.level <= LOG_WARNING { s.WARN.Output(2, fmt.Sprintln(v...)) } - return } // Warnf implement ILogger @@ -188,7 +181,6 @@ func (s *SimpleLogger) Warnf(format string, v ...interface{}) { if s.level <= LOG_WARNING { s.WARN.Output(2, fmt.Sprintf(format, v...)) } - return } // Level implement ILogger @@ -199,7 +191,6 @@ func (s *SimpleLogger) Level() LogLevel { // SetLevel implement ILogger func (s *SimpleLogger) SetLevel(l LogLevel) { s.level = l - return } // ShowSQL implement ILogger diff --git a/schemas/table_test.go b/schemas/table_test.go index 9bf10e33..0e35193f 100644 --- a/schemas/table_test.go +++ b/schemas/table_test.go @@ -27,7 +27,6 @@ var testsGetColumn = []struct { var table *Table func init() { - table = NewEmptyTable() var name string @@ -41,7 +40,6 @@ func init() { } func TestGetColumn(t *testing.T) { - for _, test := range testsGetColumn { if table.GetColumn(test.name) == nil { t.Error("Column not found!") @@ -50,7 +48,6 @@ func TestGetColumn(t *testing.T) { } func TestGetColumnIdx(t *testing.T) { - for _, test := range testsGetColumn { if table.GetColumnIdx(test.name, test.idx) == nil { t.Errorf("Column %s with idx %d not found!", test.name, test.idx) @@ -59,7 +56,6 @@ func TestGetColumnIdx(t *testing.T) { } func BenchmarkGetColumnWithToLower(b *testing.B) { - for i := 0; i < b.N; i++ { for _, test := range testsGetColumn { @@ -71,7 +67,6 @@ func BenchmarkGetColumnWithToLower(b *testing.B) { } func BenchmarkGetColumnIdxWithToLower(b *testing.B) { - for i := 0; i < b.N; i++ { for _, test := range testsGetColumn { @@ -89,7 +84,6 @@ func BenchmarkGetColumnIdxWithToLower(b *testing.B) { } func BenchmarkGetColumn(b *testing.B) { - for i := 0; i < b.N; i++ { for _, test := range testsGetColumn { if table.GetColumn(test.name) == nil { @@ -100,7 +94,6 @@ func BenchmarkGetColumn(b *testing.B) { } func BenchmarkGetColumnIdx(b *testing.B) { - for i := 0; i < b.N; i++ { for _, test := range testsGetColumn { if table.GetColumnIdx(test.name, test.idx) == nil { diff --git a/session_update.go b/session_update.go index f791bb2d..d96226da 100644 --- a/session_update.go +++ b/session_update.go @@ -457,7 +457,6 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 // FIXME: if bean is a map type, it will panic because map cannot be as map key session.afterUpdateBeans[bean] = &afterClosures } - } else { if _, ok := interface{}(bean).(AfterUpdateProcessor); ok { session.afterUpdateBeans[bean] = nil From 44f892fddca72e496e13e947cf4c28e2348bd2ba Mon Sep 17 00:00:00 2001 From: antialiasis Date: Sat, 26 Jun 2021 19:19:13 +0800 Subject: [PATCH 059/179] Ignore comments when deciding when to replace question marks. #1954 (#1955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should solve #1954 and adds some tests for it. I will note I'm not 100% clear on whether there are other edge cases that should be covered here. From what I understand the only standard SQL way to escape single quotes is to double them, which shouldn't cause any problems with this, but if some SQL flavors allow other kinds of escaping, for instance, that would probably need to be covered too for ideal results. Co-authored-by: Hlín Önnudóttir Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1955 Reviewed-by: Lunny Xiao Co-authored-by: antialiasis Co-committed-by: antialiasis --- dialects/filter.go | 36 ++++++++++++++++++++++++-- dialects/filter_test.go | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/dialects/filter.go b/dialects/filter.go index 2a36a731..bfe2e93e 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -23,13 +23,45 @@ type SeqFilter struct { func convertQuestionMark(sql, prefix string, start int) string { var buf strings.Builder var beginSingleQuote bool + var isLineComment bool + var isComment bool + var isMaybeLineComment bool + var isMaybeComment bool + var isMaybeCommentEnd bool var index = start for _, c := range sql { - if !beginSingleQuote && c == '?' { + if !beginSingleQuote && !isLineComment && !isComment && c == '?' { buf.WriteString(fmt.Sprintf("%s%v", prefix, index)) index++ } else { - if c == '\'' { + if isMaybeLineComment { + if c == '-' { + isLineComment = true + } + isMaybeLineComment = false + } else if isMaybeComment { + if c == '*' { + isComment = true + } + isMaybeComment = false + } else if isMaybeCommentEnd { + if c == '/' { + isComment = false + } + isMaybeCommentEnd = false + } else if isLineComment { + if c == '\n' { + isLineComment = false + } + } else if isComment { + if c == '*' { + isMaybeCommentEnd = true + } + } else if !beginSingleQuote && c == '-' { + isMaybeLineComment = true + } else if !beginSingleQuote && c == '/' { + isMaybeComment = true + } else if c == '\'' { beginSingleQuote = !beginSingleQuote } buf.WriteRune(c) diff --git a/dialects/filter_test.go b/dialects/filter_test.go index 7e2ef0a2..15050656 100644 --- a/dialects/filter_test.go +++ b/dialects/filter_test.go @@ -19,3 +19,60 @@ func TestSeqFilter(t *testing.T) { assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1)) } } + +func TestSeqFilterLineComment(t *testing.T) { + var kases = map[string]string{ + `SELECT * + FROM TABLE1 + WHERE foo='bar' + AND a=? -- it's a comment + AND b=?`: `SELECT * + FROM TABLE1 + WHERE foo='bar' + AND a=$1 -- it's a comment + AND b=$2`, + `SELECT * + FROM TABLE1 + WHERE foo='bar' + AND a=? -- it's a comment? + AND b=?`: `SELECT * + FROM TABLE1 + WHERE foo='bar' + AND a=$1 -- it's a comment? + AND b=$2`, + `SELECT * + FROM TABLE1 + WHERE a=? -- it's a comment? and that's okay? + AND b=?`: `SELECT * + FROM TABLE1 + WHERE a=$1 -- it's a comment? and that's okay? + AND b=$2`, + } + for sql, result := range kases { + assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1)) + } +} + +func TestSeqFilterComment(t *testing.T) { + var kases = map[string]string{ + `SELECT * + FROM TABLE1 + WHERE a=? /* it's a comment */ + AND b=?`: `SELECT * + FROM TABLE1 + WHERE a=$1 /* it's a comment */ + AND b=$2`, + `SELECT /* it's a comment * ? + More comment on the next line! */ * + FROM TABLE1 + WHERE a=? /**/ + AND b=?`: `SELECT /* it's a comment * ? + More comment on the next line! */ * + FROM TABLE1 + WHERE a=$1 /**/ + AND b=$2`, + } + for sql, result := range kases { + assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1)) + } +} From 053a0947404ffb51ad35184676e461c29b06e06d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 28 Jun 2021 22:41:54 +0800 Subject: [PATCH 060/179] refactor splitTag function (#1960) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1960 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .gitignore | 3 +- tags/parser.go | 330 +++++++++++++++---------------- tags/parser_test.go | 458 +++++++++++++++++++++++++++++++++++++++++++- tags/tag.go | 147 +++++++++----- tags/tag_test.go | 79 +++++++- 5 files changed, 774 insertions(+), 243 deletions(-) diff --git a/.gitignore b/.gitignore index a3fbadd4..a183a295 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ test.db.sql *coverage.out test.db integrations/*.sql -integrations/test_sqlite* \ No newline at end of file +integrations/test_sqlite* +cover.out \ No newline at end of file diff --git a/tags/parser.go b/tags/parser.go index ff329daa..599e9e0e 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -7,7 +7,6 @@ package tags import ( "encoding/gob" "errors" - "fmt" "reflect" "strings" "sync" @@ -23,7 +22,7 @@ import ( var ( // ErrUnsupportedType represents an unsupported type error - ErrUnsupportedType = errors.New("Unsupported type") + ErrUnsupportedType = errors.New("unsupported type") ) // Parser represents a parser for xorm tag @@ -125,6 +124,145 @@ func addIndex(indexName string, table *schemas.Table, col *schemas.Column, index } } +var ErrIgnoreField = errors.New("field will be ignored") + +func (parser *Parser) parseFieldWithNoTag(field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { + var sqlType schemas.SQLType + if fieldValue.CanAddr() { + if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { + sqlType = schemas.SQLType{Name: schemas.Text} + } + } + if _, ok := fieldValue.Interface().(convert.Conversion); ok { + sqlType = schemas.SQLType{Name: schemas.Text} + } else { + sqlType = schemas.Type2SQLType(field.Type) + } + col := schemas.NewColumn(parser.columnMapper.Obj2Table(field.Name), + field.Name, sqlType, sqlType.DefaultLength, + sqlType.DefaultLength2, true) + + if field.Type.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { + col.IsAutoIncrement = true + col.IsPrimaryKey = true + col.Nullable = false + } + return col, nil +} + +func (parser *Parser) parseFieldWithTags(table *schemas.Table, field reflect.StructField, fieldValue reflect.Value, tags []tag) (*schemas.Column, error) { + var col = &schemas.Column{ + FieldName: field.Name, + Nullable: true, + IsPrimaryKey: false, + IsAutoIncrement: false, + MapType: schemas.TWOSIDES, + Indexes: make(map[string]int), + DefaultIsEmpty: true, + } + + var ctx = Context{ + table: table, + col: col, + fieldValue: fieldValue, + indexNames: make(map[string]int), + parser: parser, + } + + for j, tag := range tags { + if ctx.ignoreNext { + ctx.ignoreNext = false + continue + } + + ctx.tag = tag + ctx.tagUname = strings.ToUpper(tag.name) + + if j > 0 { + ctx.preTag = strings.ToUpper(tags[j-1].name) + } + if j < len(tags)-1 { + ctx.nextTag = tags[j+1].name + } else { + ctx.nextTag = "" + } + + if h, ok := parser.handlers[ctx.tagUname]; ok { + if err := h(&ctx); err != nil { + return nil, err + } + } else { + if strings.HasPrefix(ctx.tag.name, "'") && strings.HasSuffix(ctx.tag.name, "'") { + col.Name = ctx.tag.name[1 : len(ctx.tag.name)-1] + } else { + col.Name = ctx.tag.name + } + } + + if ctx.hasCacheTag { + if parser.cacherMgr.GetDefaultCacher() != nil { + parser.cacherMgr.SetCacher(table.Name, parser.cacherMgr.GetDefaultCacher()) + } else { + parser.cacherMgr.SetCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) + } + } + if ctx.hasNoCacheTag { + parser.cacherMgr.SetCacher(table.Name, nil) + } + } + + if col.SQLType.Name == "" { + col.SQLType = schemas.Type2SQLType(field.Type) + } + parser.dialect.SQLType(col) + if col.Length == 0 { + col.Length = col.SQLType.DefaultLength + } + if col.Length2 == 0 { + col.Length2 = col.SQLType.DefaultLength2 + } + if col.Name == "" { + col.Name = parser.columnMapper.Obj2Table(field.Name) + } + + if ctx.isUnique { + ctx.indexNames[col.Name] = schemas.UniqueType + } else if ctx.isIndex { + ctx.indexNames[col.Name] = schemas.IndexType + } + + for indexName, indexType := range ctx.indexNames { + addIndex(indexName, table, col, indexType) + } + + return col, nil +} + +func (parser *Parser) parseField(table *schemas.Table, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { + var ( + tag = field.Tag + ormTagStr = strings.TrimSpace(tag.Get(parser.identifier)) + ) + if ormTagStr == "-" { + return nil, ErrIgnoreField + } + if ormTagStr == "" { + return parser.parseFieldWithNoTag(field, fieldValue) + } + tags, err := splitTag(ormTagStr) + if err != nil { + return nil, err + } + return parser.parseFieldWithTags(table, field, fieldValue, tags) +} + +func isNotTitle(n string) bool { + for _, c := range n { + return unicode.IsLower(c) + } + return true +} + // Parse parses a struct as a table information func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { t := v.Type() @@ -140,193 +278,25 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table.Type = t table.Name = names.GetTableName(parser.tableMapper, v) - var idFieldColName string - var hasCacheTag, hasNoCacheTag bool - for i := 0; i < t.NumField(); i++ { - var isUnexportField bool - for _, c := range t.Field(i).Name { - if unicode.IsLower(c) { - isUnexportField = true - } - break - } - if isUnexportField { + if isNotTitle(t.Field(i).Name) { continue } - tag := t.Field(i).Tag - ormTagStr := tag.Get(parser.identifier) - var col *schemas.Column - fieldValue := v.Field(i) - fieldType := fieldValue.Type() + var ( + field = t.Field(i) + fieldValue = v.Field(i) + ) - if ormTagStr != "" { - col = &schemas.Column{ - FieldName: t.Field(i).Name, - Nullable: true, - IsPrimaryKey: false, - IsAutoIncrement: false, - MapType: schemas.TWOSIDES, - Indexes: make(map[string]int), - DefaultIsEmpty: true, - } - tags := splitTag(ormTagStr) - - if len(tags) > 0 { - if tags[0] == "-" { - continue - } - - var ctx = Context{ - table: table, - col: col, - fieldValue: fieldValue, - indexNames: make(map[string]int), - parser: parser, - } - - if strings.HasPrefix(strings.ToUpper(tags[0]), "EXTENDS") { - pStart := strings.Index(tags[0], "(") - if pStart > -1 && strings.HasSuffix(tags[0], ")") { - var tagPrefix = strings.TrimFunc(tags[0][pStart+1:len(tags[0])-1], func(r rune) bool { - return r == '\'' || r == '"' - }) - - ctx.params = []string{tagPrefix} - } - - if err := ExtendsTagHandler(&ctx); err != nil { - return nil, err - } - continue - } - - for j, key := range tags { - if ctx.ignoreNext { - ctx.ignoreNext = false - continue - } - - k := strings.ToUpper(key) - ctx.tagName = k - ctx.params = []string{} - - pStart := strings.Index(k, "(") - if pStart == 0 { - return nil, errors.New("( could not be the first character") - } - if pStart > -1 { - if !strings.HasSuffix(k, ")") { - return nil, fmt.Errorf("field %s tag %s cannot match ) character", col.FieldName, key) - } - - ctx.tagName = k[:pStart] - ctx.params = strings.Split(key[pStart+1:len(k)-1], ",") - } - - if j > 0 { - ctx.preTag = strings.ToUpper(tags[j-1]) - } - if j < len(tags)-1 { - ctx.nextTag = tags[j+1] - } else { - ctx.nextTag = "" - } - - if h, ok := parser.handlers[ctx.tagName]; ok { - if err := h(&ctx); err != nil { - return nil, err - } - } else { - if strings.HasPrefix(key, "'") && strings.HasSuffix(key, "'") { - col.Name = key[1 : len(key)-1] - } else { - col.Name = key - } - } - - if ctx.hasCacheTag { - hasCacheTag = true - } - if ctx.hasNoCacheTag { - hasNoCacheTag = true - } - } - - if col.SQLType.Name == "" { - col.SQLType = schemas.Type2SQLType(fieldType) - } - parser.dialect.SQLType(col) - if col.Length == 0 { - col.Length = col.SQLType.DefaultLength - } - if col.Length2 == 0 { - col.Length2 = col.SQLType.DefaultLength2 - } - if col.Name == "" { - col.Name = parser.columnMapper.Obj2Table(t.Field(i).Name) - } - - if ctx.isUnique { - ctx.indexNames[col.Name] = schemas.UniqueType - } else if ctx.isIndex { - ctx.indexNames[col.Name] = schemas.IndexType - } - - for indexName, indexType := range ctx.indexNames { - addIndex(indexName, table, col, indexType) - } - } - } else { - var sqlType schemas.SQLType - if fieldValue.CanAddr() { - if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - sqlType = schemas.SQLType{Name: schemas.Text} - } - } - if _, ok := fieldValue.Interface().(convert.Conversion); ok { - sqlType = schemas.SQLType{Name: schemas.Text} - } else { - sqlType = schemas.Type2SQLType(fieldType) - } - col = schemas.NewColumn(parser.columnMapper.Obj2Table(t.Field(i).Name), - t.Field(i).Name, sqlType, sqlType.DefaultLength, - sqlType.DefaultLength2, true) - - if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { - idFieldColName = col.Name - } - } - if col.IsAutoIncrement { - col.Nullable = false + col, err := parser.parseField(table, field, fieldValue) + if err == ErrIgnoreField { + continue + } else if err != nil { + return nil, err } table.AddColumn(col) } // end for - if idFieldColName != "" && len(table.PrimaryKeys) == 0 { - col := table.GetColumn(idFieldColName) - col.IsPrimaryKey = true - col.IsAutoIncrement = true - col.Nullable = false - table.PrimaryKeys = append(table.PrimaryKeys, col.Name) - table.AutoIncrement = col.Name - } - - if hasCacheTag { - if parser.cacherMgr.GetDefaultCacher() != nil { // !nash! use engine's cacher if provided - //engine.logger.Info("enable cache on table:", table.Name) - parser.cacherMgr.SetCacher(table.Name, parser.cacherMgr.GetDefaultCacher()) - } else { - //engine.logger.Info("enable LRU cache on table:", table.Name) - parser.cacherMgr.SetCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) - } - } - if hasNoCacheTag { - //engine.logger.Info("disable cache on table:", table.Name) - parser.cacherMgr.SetCacher(table.Name, nil) - } - return table, nil } diff --git a/tags/parser_test.go b/tags/parser_test.go index 5add1e13..70c57692 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -6,12 +6,16 @@ package tags import ( "reflect" + "strings" "testing" + "time" - "github.com/stretchr/testify/assert" "xorm.io/xorm/caches" "xorm.io/xorm/dialects" "xorm.io/xorm/names" + "xorm.io/xorm/schemas" + + "github.com/stretchr/testify/assert" ) type ParseTableName1 struct{} @@ -80,7 +84,7 @@ func TestParseWithOtherIdentifier(t *testing.T) { parser := NewParser( "xorm", dialects.QueryDialect("mysql"), - names.GonicMapper{}, + names.SameMapper{}, names.SnakeMapper{}, caches.NewManager(), ) @@ -88,13 +92,461 @@ func TestParseWithOtherIdentifier(t *testing.T) { type StructWithDBTag struct { FieldFoo string `db:"foo"` } + parser.SetIdentifier("db") table, err := parser.Parse(reflect.ValueOf(new(StructWithDBTag))) assert.NoError(t, err) - assert.EqualValues(t, "struct_with_db_tag", table.Name) + assert.EqualValues(t, "StructWithDBTag", table.Name) assert.EqualValues(t, 1, len(table.Columns())) for _, col := range table.Columns() { assert.EqualValues(t, "foo", col.Name) } } + +func TestParseWithIgnore(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SameMapper{}, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithIgnoreTag struct { + FieldFoo string `db:"-"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithIgnoreTag))) + assert.NoError(t, err) + assert.EqualValues(t, "StructWithIgnoreTag", table.Name) + assert.EqualValues(t, 0, len(table.Columns())) +} + +func TestParseWithAutoincrement(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithAutoIncrement struct { + ID int64 + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithAutoIncrement))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_auto_increment", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "id", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].IsAutoIncrement) + assert.True(t, table.Columns()[0].IsPrimaryKey) +} + +func TestParseWithAutoincrement2(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithAutoIncrement2 struct { + ID int64 `db:"pk autoincr"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithAutoIncrement2))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_auto_increment2", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "id", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].IsAutoIncrement) + assert.True(t, table.Columns()[0].IsPrimaryKey) + assert.False(t, table.Columns()[0].Nullable) +} + +func TestParseWithNullable(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithNullable struct { + Name string `db:"notnull"` + FullName string `db:"null comment('column comment,字段注释')"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithNullable))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_nullable", table.Name) + assert.EqualValues(t, 2, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.EqualValues(t, "full_name", table.Columns()[1].Name) + assert.False(t, table.Columns()[0].Nullable) + assert.True(t, table.Columns()[1].Nullable) + assert.EqualValues(t, "column comment,字段注释", table.Columns()[1].Comment) +} + +func TestParseWithTimes(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithTimes struct { + Name string `db:"notnull"` + CreatedAt time.Time `db:"created"` + UpdatedAt time.Time `db:"updated"` + DeletedAt time.Time `db:"deleted"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithTimes))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_times", table.Name) + assert.EqualValues(t, 4, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.EqualValues(t, "created_at", table.Columns()[1].Name) + assert.EqualValues(t, "updated_at", table.Columns()[2].Name) + assert.EqualValues(t, "deleted_at", table.Columns()[3].Name) + assert.False(t, table.Columns()[0].Nullable) + assert.True(t, table.Columns()[1].Nullable) + assert.True(t, table.Columns()[1].IsCreated) + assert.True(t, table.Columns()[2].Nullable) + assert.True(t, table.Columns()[2].IsUpdated) + assert.True(t, table.Columns()[3].Nullable) + assert.True(t, table.Columns()[3].IsDeleted) +} + +func TestParseWithExtends(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithEmbed struct { + Name string + CreatedAt time.Time `db:"created"` + UpdatedAt time.Time `db:"updated"` + DeletedAt time.Time `db:"deleted"` + } + + type StructWithExtends struct { + SW StructWithEmbed `db:"extends"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithExtends))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_extends", table.Name) + assert.EqualValues(t, 4, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.EqualValues(t, "created_at", table.Columns()[1].Name) + assert.EqualValues(t, "updated_at", table.Columns()[2].Name) + assert.EqualValues(t, "deleted_at", table.Columns()[3].Name) + assert.True(t, table.Columns()[0].Nullable) + assert.True(t, table.Columns()[1].Nullable) + assert.True(t, table.Columns()[1].IsCreated) + assert.True(t, table.Columns()[2].Nullable) + assert.True(t, table.Columns()[2].IsUpdated) + assert.True(t, table.Columns()[3].Nullable) + assert.True(t, table.Columns()[3].IsDeleted) +} + +func TestParseWithCache(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithCache struct { + Name string `db:"cache"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithCache))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_cache", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].Nullable) + cacher := parser.cacherMgr.GetCacher(table.Name) + assert.NotNil(t, cacher) +} + +func TestParseWithNoCache(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithNoCache struct { + Name string `db:"nocache"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithNoCache))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_no_cache", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].Nullable) + cacher := parser.cacherMgr.GetCacher(table.Name) + assert.Nil(t, cacher) +} + +func TestParseWithEnum(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithEnum struct { + Name string `db:"enum('alice', 'bob')"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithEnum))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_enum", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].Nullable) + assert.EqualValues(t, schemas.Enum, strings.ToUpper(table.Columns()[0].SQLType.Name)) + assert.EqualValues(t, map[string]int{ + "alice": 0, + "bob": 1, + }, table.Columns()[0].EnumOptions) +} + +func TestParseWithSet(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithSet struct { + Name string `db:"set('alice', 'bob')"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithSet))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_set", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].Nullable) + assert.EqualValues(t, schemas.Set, strings.ToUpper(table.Columns()[0].SQLType.Name)) + assert.EqualValues(t, map[string]int{ + "alice": 0, + "bob": 1, + }, table.Columns()[0].SetOptions) +} + +func TestParseWithIndex(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithIndex struct { + Name string `db:"index"` + Name2 string `db:"index(s)"` + Name3 string `db:"unique"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithIndex))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_index", table.Name) + assert.EqualValues(t, 3, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.EqualValues(t, "name2", table.Columns()[1].Name) + assert.EqualValues(t, "name3", table.Columns()[2].Name) + assert.True(t, table.Columns()[0].Nullable) + assert.True(t, table.Columns()[1].Nullable) + assert.True(t, table.Columns()[2].Nullable) + assert.EqualValues(t, 1, len(table.Columns()[0].Indexes)) + assert.EqualValues(t, 1, len(table.Columns()[1].Indexes)) + assert.EqualValues(t, 1, len(table.Columns()[2].Indexes)) +} + +func TestParseWithVersion(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithVersion struct { + Name string + Version int `db:"version"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithVersion))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_version", table.Name) + assert.EqualValues(t, 2, len(table.Columns())) + assert.EqualValues(t, "name", table.Columns()[0].Name) + assert.EqualValues(t, "version", table.Columns()[1].Name) + assert.True(t, table.Columns()[0].Nullable) + assert.True(t, table.Columns()[1].Nullable) + assert.True(t, table.Columns()[1].IsVersion) +} + +func TestParseWithLocale(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithLocale struct { + UTCLocale time.Time `db:"utc"` + LocalLocale time.Time `db:"local"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithLocale))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_locale", table.Name) + assert.EqualValues(t, 2, len(table.Columns())) + assert.EqualValues(t, "utc_locale", table.Columns()[0].Name) + assert.EqualValues(t, "local_locale", table.Columns()[1].Name) + assert.EqualValues(t, time.UTC, table.Columns()[0].TimeZone) + assert.EqualValues(t, time.Local, table.Columns()[1].TimeZone) +} + +func TestParseWithDefault(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.GonicMapper{}, + caches.NewManager(), + ) + + type StructWithDefault struct { + Default1 time.Time `db:"default '1970-01-01 00:00:00'"` + Default2 time.Time `db:"default(CURRENT_TIMESTAMP)"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithDefault))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_default", table.Name) + assert.EqualValues(t, 2, len(table.Columns())) + assert.EqualValues(t, "default1", table.Columns()[0].Name) + assert.EqualValues(t, "default2", table.Columns()[1].Name) + assert.EqualValues(t, "'1970-01-01 00:00:00'", table.Columns()[0].Default) + assert.EqualValues(t, "CURRENT_TIMESTAMP", table.Columns()[1].Default) +} + +func TestParseWithOnlyToDB(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.GonicMapper{ + "DB": true, + }, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithOnlyToDB struct { + Default1 time.Time `db:"->"` + Default2 time.Time `db:"<-"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithOnlyToDB))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_only_to_db", table.Name) + assert.EqualValues(t, 2, len(table.Columns())) + assert.EqualValues(t, "default1", table.Columns()[0].Name) + assert.EqualValues(t, "default2", table.Columns()[1].Name) + assert.EqualValues(t, schemas.ONLYTODB, table.Columns()[0].MapType) + assert.EqualValues(t, schemas.ONLYFROMDB, table.Columns()[1].MapType) +} + +func TestParseWithJSON(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.GonicMapper{ + "JSON": true, + }, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithJSON struct { + Default1 []string `db:"json"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithJSON))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_json", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "default1", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].IsJSON) +} + +func TestParseWithSQLType(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.GonicMapper{ + "SQL": true, + }, + names.GonicMapper{ + "UUID": true, + }, + caches.NewManager(), + ) + + type StructWithSQLType struct { + Col1 string `db:"varchar(32)"` + Col2 string `db:"char(32)"` + Int int64 `db:"bigint"` + DateTime time.Time `db:"datetime"` + UUID string `db:"uuid"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithSQLType))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_sql_type", table.Name) + assert.EqualValues(t, 5, len(table.Columns())) + assert.EqualValues(t, "col1", table.Columns()[0].Name) + assert.EqualValues(t, "col2", table.Columns()[1].Name) + assert.EqualValues(t, "int", table.Columns()[2].Name) + assert.EqualValues(t, "date_time", table.Columns()[3].Name) + assert.EqualValues(t, "uuid", table.Columns()[4].Name) + + assert.EqualValues(t, "VARCHAR", table.Columns()[0].SQLType.Name) + assert.EqualValues(t, "CHAR", table.Columns()[1].SQLType.Name) + assert.EqualValues(t, "BIGINT", table.Columns()[2].SQLType.Name) + assert.EqualValues(t, "DATETIME", table.Columns()[3].SQLType.Name) + assert.EqualValues(t, "UUID", table.Columns()[4].SQLType.Name) +} diff --git a/tags/tag.go b/tags/tag.go index bb5b5838..d8d9bb46 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -14,30 +14,74 @@ import ( "xorm.io/xorm/schemas" ) -func splitTag(tag string) (tags []string) { - tag = strings.TrimSpace(tag) - var hasQuote = false - var lastIdx = 0 - for i, t := range tag { - if t == '\'' { - hasQuote = !hasQuote - } else if t == ' ' { - if lastIdx < i && !hasQuote { - tags = append(tags, strings.TrimSpace(tag[lastIdx:i])) - lastIdx = i + 1 +type tag struct { + name string + params []string +} + +func splitTag(tagStr string) ([]tag, error) { + tagStr = strings.TrimSpace(tagStr) + var ( + inQuote bool + inBigQuote bool + lastIdx int + curTag tag + paramStart int + tags []tag + ) + for i, t := range tagStr { + switch t { + case '\'': + inQuote = !inQuote + case ' ': + if !inQuote && !inBigQuote { + if lastIdx < i { + if curTag.name == "" { + curTag.name = tagStr[lastIdx:i] + } + tags = append(tags, curTag) + lastIdx = i + 1 + curTag = tag{} + } else if lastIdx == i { + lastIdx = i + 1 + } + } else if inBigQuote && !inQuote { + paramStart = i + 1 + } + case ',': + if !inQuote && !inBigQuote { + return nil, fmt.Errorf("comma[%d] of %s should be in quote or big quote", i, tagStr) + } + if !inQuote && inBigQuote { + curTag.params = append(curTag.params, strings.TrimSpace(tagStr[paramStart:i])) + paramStart = i + 1 + } + case '(': + inBigQuote = true + if !inQuote { + curTag.name = tagStr[lastIdx:i] + paramStart = i + 1 + } + case ')': + inBigQuote = false + if !inQuote { + curTag.params = append(curTag.params, tagStr[paramStart:i]) } } } - if lastIdx < len(tag) { - tags = append(tags, strings.TrimSpace(tag[lastIdx:])) + if lastIdx < len(tagStr) { + if curTag.name == "" { + curTag.name = tagStr[lastIdx:] + } + tags = append(tags, curTag) } - return + return tags, nil } // Context represents a context for xorm tag parse. type Context struct { - tagName string - params []string + tag + tagUname string preTag, nextTag string table *schemas.Table col *schemas.Column @@ -76,6 +120,7 @@ var ( "CACHE": CacheTagHandler, "NOCACHE": NoCacheTagHandler, "COMMENT": CommentTagHandler, + "EXTENDS": ExtendsTagHandler, } ) @@ -124,6 +169,7 @@ func NotNullTagHandler(ctx *Context) error { // AutoIncrTagHandler describes autoincr tag handler func AutoIncrTagHandler(ctx *Context) error { ctx.col.IsAutoIncrement = true + ctx.col.Nullable = false /* if len(ctx.params) > 0 { autoStartInt, err := strconv.Atoi(ctx.params[0]) @@ -225,41 +271,44 @@ func CommentTagHandler(ctx *Context) error { // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *Context) error { - ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName} - if strings.EqualFold(ctx.tagName, "JSON") { + ctx.col.SQLType = schemas.SQLType{Name: ctx.tagUname} + if ctx.tagUname == "JSON" { ctx.col.IsJSON = true } - if len(ctx.params) > 0 { - if ctx.tagName == schemas.Enum { - ctx.col.EnumOptions = make(map[string]int) - for k, v := range ctx.params { - v = strings.TrimSpace(v) - v = strings.Trim(v, "'") - ctx.col.EnumOptions[v] = k + if len(ctx.params) == 0 { + return nil + } + + switch ctx.tagUname { + case schemas.Enum: + ctx.col.EnumOptions = make(map[string]int) + for k, v := range ctx.params { + v = strings.TrimSpace(v) + v = strings.Trim(v, "'") + ctx.col.EnumOptions[v] = k + } + case schemas.Set: + ctx.col.SetOptions = make(map[string]int) + for k, v := range ctx.params { + v = strings.TrimSpace(v) + v = strings.Trim(v, "'") + ctx.col.SetOptions[v] = k + } + default: + var err error + if len(ctx.params) == 2 { + ctx.col.Length, err = strconv.Atoi(ctx.params[0]) + if err != nil { + return err } - } else if ctx.tagName == schemas.Set { - ctx.col.SetOptions = make(map[string]int) - for k, v := range ctx.params { - v = strings.TrimSpace(v) - v = strings.Trim(v, "'") - ctx.col.SetOptions[v] = k + ctx.col.Length2, err = strconv.Atoi(ctx.params[1]) + if err != nil { + return err } - } else { - var err error - if len(ctx.params) == 2 { - ctx.col.Length, err = strconv.Atoi(ctx.params[0]) - if err != nil { - return err - } - ctx.col.Length2, err = strconv.Atoi(ctx.params[1]) - if err != nil { - return err - } - } else if len(ctx.params) == 1 { - ctx.col.Length, err = strconv.Atoi(ctx.params[0]) - if err != nil { - return err - } + } else if len(ctx.params) == 1 { + ctx.col.Length, err = strconv.Atoi(ctx.params[0]) + if err != nil { + return err } } } @@ -293,7 +342,7 @@ func ExtendsTagHandler(ctx *Context) error { var tagPrefix = ctx.col.FieldName if len(ctx.params) > 0 { col.Nullable = isPtr - tagPrefix = ctx.params[0] + tagPrefix = strings.Trim(ctx.params[0], "'") if col.IsPrimaryKey { col.Name = ctx.col.FieldName col.IsPrimaryKey = false @@ -315,7 +364,7 @@ func ExtendsTagHandler(ctx *Context) error { default: //TODO: warning } - return nil + return ErrIgnoreField } // CacheTagHandler describes cache tag handler diff --git a/tags/tag_test.go b/tags/tag_test.go index 5775b40a..3ceeefd1 100644 --- a/tags/tag_test.go +++ b/tags/tag_test.go @@ -7,24 +7,83 @@ package tags import ( "testing" - "xorm.io/xorm/internal/utils" + "github.com/stretchr/testify/assert" ) func TestSplitTag(t *testing.T) { var cases = []struct { tag string - tags []string + tags []tag }{ - {"not null default '2000-01-01 00:00:00' TIMESTAMP", []string{"not", "null", "default", "'2000-01-01 00:00:00'", "TIMESTAMP"}}, - {"TEXT", []string{"TEXT"}}, - {"default('2000-01-01 00:00:00')", []string{"default('2000-01-01 00:00:00')"}}, - {"json binary", []string{"json", "binary"}}, + {"not null default '2000-01-01 00:00:00' TIMESTAMP", []tag{ + { + name: "not", + }, + { + name: "null", + }, + { + name: "default", + }, + { + name: "'2000-01-01 00:00:00'", + }, + { + name: "TIMESTAMP", + }, + }, + }, + {"TEXT", []tag{ + { + name: "TEXT", + }, + }, + }, + {"default('2000-01-01 00:00:00')", []tag{ + { + name: "default", + params: []string{ + "'2000-01-01 00:00:00'", + }, + }, + }, + }, + {"json binary", []tag{ + { + name: "json", + }, + { + name: "binary", + }, + }, + }, + {"numeric(10, 2)", []tag{ + { + name: "numeric", + params: []string{"10", "2"}, + }, + }, + }, + {"numeric(10, 2) notnull", []tag{ + { + name: "numeric", + params: []string{"10", "2"}, + }, + { + name: "notnull", + }, + }, + }, } for _, kase := range cases { - tags := splitTag(kase.tag) - if !utils.SliceEq(tags, kase.tags) { - t.Fatalf("[%d]%v is not equal [%d]%v", len(tags), tags, len(kase.tags), kase.tags) - } + t.Run(kase.tag, func(t *testing.T) { + tags, err := splitTag(kase.tag) + assert.NoError(t, err) + assert.EqualValues(t, len(tags), len(kase.tags)) + for i := 0; i < len(tags); i++ { + assert.Equal(t, tags[i], kase.tags[i]) + } + }) } } From 8f8195a86b7ae503a7250b0a92a47b3d35b32488 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 29 Jun 2021 14:32:29 +0800 Subject: [PATCH 061/179] Improve get field value of bean (#1961) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1961 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 4 +-- convert.go | 52 ++++---------------------------- engine.go | 6 +--- internal/statements/statement.go | 2 ++ internal/statements/update.go | 3 ++ schemas/column.go | 43 ++++++-------------------- schemas/table.go | 19 +----------- session.go | 3 ++ session_convert.go | 10 +----- session_insert.go | 12 +++----- session_update.go | 13 +++----- tags/parser.go | 22 ++++++-------- tags/tag.go | 1 + 13 files changed, 49 insertions(+), 141 deletions(-) diff --git a/.drone.yml b/.drone.yml index 9b4ffe9a..4f84d7fa 100644 --- a/.drone.yml +++ b/.drone.yml @@ -249,11 +249,11 @@ volumes: services: - name: mssql pull: always - image: microsoft/mssql-server-linux:latest + image: mcr.microsoft.com/mssql/server:latest environment: ACCEPT_EULA: Y SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Developer + MSSQL_PID: Standard --- kind: pipeline diff --git a/convert.go b/convert.go index ee5b6029..b7f30cad 100644 --- a/convert.go +++ b/convert.go @@ -175,7 +175,10 @@ func convertAssign(dest, src interface{}) error { return nil } - dpv := reflect.ValueOf(dest) + return convertAssignV(reflect.ValueOf(dest), src) +} + +func convertAssignV(dpv reflect.Value, src interface{}) error { if dpv.Kind() != reflect.Ptr { return errors.New("destination not a pointer") } @@ -183,9 +186,7 @@ func convertAssign(dest, src interface{}) error { return errNilPtr } - if !sv.IsValid() { - sv = reflect.ValueOf(src) - } + var sv = reflect.ValueOf(src) dv := reflect.Indirect(dpv) if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) { @@ -244,7 +245,7 @@ func convertAssign(dest, src interface{}) error { return nil } - return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) + return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dpv.Interface()) } func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { @@ -375,44 +376,3 @@ func str2PK(s string, tp reflect.Type) (interface{}, error) { } return v.Interface(), nil } - -func int64ToIntValue(id int64, tp reflect.Type) reflect.Value { - var v interface{} - kind := tp.Kind() - - if kind == reflect.Ptr { - kind = tp.Elem().Kind() - } - - switch kind { - case reflect.Int16: - temp := int16(id) - v = &temp - case reflect.Int32: - temp := int32(id) - v = &temp - case reflect.Int: - temp := int(id) - v = &temp - case reflect.Int64: - temp := id - v = &temp - case reflect.Uint16: - temp := uint16(id) - v = &temp - case reflect.Uint32: - temp := uint32(id) - v = &temp - case reflect.Uint64: - temp := uint64(id) - v = &temp - case reflect.Uint: - temp := uint(id) - v = &temp - } - - if tp.Kind() == reflect.Ptr { - return reflect.ValueOf(v).Convert(tp) - } - return reflect.ValueOf(v).Elem().Convert(tp) -} diff --git a/engine.go b/engine.go index 649ec1a2..76ce8f1a 100644 --- a/engine.go +++ b/engine.go @@ -652,11 +652,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return errors.New("unknown column error") } - fields := strings.Split(col.FieldName, ".") - field := dataStruct - for _, fieldName := range fields { - field = field.FieldByName(fieldName) - } + field := dataStruct.FieldByIndex(col.FieldIndex) temp += "," + formatColumnValue(dstDialect, field.Interface(), col) } _, err = io.WriteString(w, temp[1:]+");\n") diff --git a/internal/statements/statement.go b/internal/statements/statement.go index ca59817b..b1a5ed3c 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -734,6 +734,8 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, //engine.logger.Warn(err) } continue + } else if fieldValuePtr == nil { + continue } if col.IsDeleted && !unscoped { // tag "deleted" is enabled diff --git a/internal/statements/update.go b/internal/statements/update.go index 251880b2..06cf0689 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -88,6 +88,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, if err != nil { return nil, nil, err } + if fieldValuePtr == nil { + continue + } fieldValue := *fieldValuePtr fieldType := reflect.TypeOf(fieldValue.Interface()) diff --git a/schemas/column.go b/schemas/column.go index 24b53802..4bbb6c2d 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -6,10 +6,8 @@ package schemas import ( "errors" - "fmt" "reflect" "strconv" - "strings" "time" ) @@ -25,6 +23,7 @@ type Column struct { Name string TableName string FieldName string // Available only when parsed from a struct + FieldIndex []int // Available only when parsed from a struct SQLType SQLType IsJSON bool Length int @@ -83,41 +82,17 @@ func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { // ValueOfV returns column's filed of struct's value accept reflevt value func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { - var fieldValue reflect.Value - fieldPath := strings.Split(col.FieldName, ".") - - if dataStruct.Type().Kind() == reflect.Map { - keyValue := reflect.ValueOf(fieldPath[len(fieldPath)-1]) - fieldValue = dataStruct.MapIndex(keyValue) - return &fieldValue, nil - } else if dataStruct.Type().Kind() == reflect.Interface { - structValue := reflect.ValueOf(dataStruct.Interface()) - dataStruct = &structValue - } - - level := len(fieldPath) - fieldValue = dataStruct.FieldByName(fieldPath[0]) - for i := 0; i < level-1; i++ { - if !fieldValue.IsValid() { - break - } - if fieldValue.Kind() == reflect.Struct { - fieldValue = fieldValue.FieldByName(fieldPath[i+1]) - } else if fieldValue.Kind() == reflect.Ptr { - if fieldValue.IsNil() { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + var v = *dataStruct + for _, i := range col.FieldIndex { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) } - fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) - } else { - return nil, fmt.Errorf("field %v is not valid", col.FieldName) + v = v.Elem() } + v = v.FieldByIndex([]int{i}) } - - if !fieldValue.IsValid() { - return nil, fmt.Errorf("field %v is not valid", col.FieldName) - } - - return &fieldValue, nil + return &v, nil } // ConvertID converts id content to suitable type according column type diff --git a/schemas/table.go b/schemas/table.go index bfa517aa..91b33e06 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -5,7 +5,6 @@ package schemas import ( - "fmt" "reflect" "strconv" "strings" @@ -159,24 +158,8 @@ func (table *Table) IDOfV(rv reflect.Value) (PK, error) { for i, col := range table.PKColumns() { var err error - fieldName := col.FieldName - for { - parts := strings.SplitN(fieldName, ".", 2) - if len(parts) == 1 { - break - } + pkField := v.FieldByIndex(col.FieldIndex) - v = v.FieldByName(parts[0]) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - if v.Kind() != reflect.Struct { - return nil, fmt.Errorf("Unsupported read value of column %s from field %s", col.Name, col.FieldName) - } - fieldName = parts[1] - } - - pkField := v.FieldByName(fieldName) switch pkField.Kind() { case reflect.String: pk[i], err = col.ConvertID(pkField.String()) diff --git a/session.go b/session.go index d5ccb6dc..6df9e20d 100644 --- a/session.go +++ b/session.go @@ -375,6 +375,9 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *s if err != nil { return nil, err } + if fieldValue == nil { + return nil, ErrFieldIsNotValid{key, table.Name} + } if !fieldValue.IsValid() || !fieldValue.CanSet() { return nil, ErrFieldIsNotValid{key, table.Name} diff --git a/session_convert.go b/session_convert.go index a6839947..b8218a77 100644 --- a/session_convert.go +++ b/session_convert.go @@ -35,27 +35,20 @@ func (session *Session) str2Time(col *schemas.Column, data string) (outTime time sd, err := strconv.ParseInt(sdata, 10, 64) if err == nil { x = time.Unix(sd, 0) - //session.engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) - } else { - //session.engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } } else if len(sdata) > 19 && strings.Contains(sdata, "-") { x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc) - session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.Name, x, sdata) if err != nil { x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc) - //session.engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } if err != nil { x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc) - //session.engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } } else if len(sdata) == 19 && strings.Contains(sdata, "-") { x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc) - //session.engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) - //session.engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else if col.SQLType.Name == schemas.Time { if strings.Contains(sdata, " ") { ssd := strings.Split(sdata, " ") @@ -69,7 +62,6 @@ func (session *Session) str2Time(col *schemas.Column, data string) (outTime time st := fmt.Sprintf("2006-01-02 %v", sdata) x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc) - //session.engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else { outErr = fmt.Errorf("unsupported time format %v", sdata) return diff --git a/session_insert.go b/session_insert.go index 5f968151..82d91969 100644 --- a/session_insert.go +++ b/session_insert.go @@ -374,9 +374,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - aiValue.Set(int64ToIntValue(id, aiValue.Type())) - - return 1, nil + return 1, convertAssignV(aiValue.Addr(), id) } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.URI().DBType == schemas.POSTGRES || session.engine.dialect.URI().DBType == schemas.MSSQL) { res, err := session.queryBytes(sqlStr, args...) @@ -416,9 +414,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - aiValue.Set(int64ToIntValue(id, aiValue.Type())) - - return 1, nil + return 1, convertAssignV(aiValue.Addr(), id) } res, err := session.exec(sqlStr, args...) @@ -458,7 +454,9 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return res.RowsAffected() } - aiValue.Set(int64ToIntValue(id, aiValue.Type())) + if err := convertAssignV(aiValue.Addr(), id); err != nil { + return 0, err + } return res.RowsAffected() } diff --git a/session_update.go b/session_update.go index d96226da..78907e43 100644 --- a/session_update.go +++ b/session_update.go @@ -280,15 +280,12 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 k = ct.Elem().Kind() } if k == reflect.Struct { - var refTable = session.statement.RefTable - if refTable == nil { - refTable, err = session.engine.TableInfo(condiBean[0]) - if err != nil { - return 0, err - } + condTable, err := session.engine.TableInfo(condiBean[0]) + if err != nil { + return 0, err } - var err error - autoCond, err = session.statement.BuildConds(refTable, condiBean[0], true, true, false, true, false) + + autoCond, err = session.statement.BuildConds(condTable, condiBean[0], true, true, false, true, false) if err != nil { return 0, err } diff --git a/tags/parser.go b/tags/parser.go index 599e9e0e..d701e316 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -126,7 +126,7 @@ func addIndex(indexName string, table *schemas.Table, col *schemas.Column, index var ErrIgnoreField = errors.New("field will be ignored") -func (parser *Parser) parseFieldWithNoTag(field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { +func (parser *Parser) parseFieldWithNoTag(fieldIndex int, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { var sqlType schemas.SQLType if fieldValue.CanAddr() { if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { @@ -141,6 +141,7 @@ func (parser *Parser) parseFieldWithNoTag(field reflect.StructField, fieldValue col := schemas.NewColumn(parser.columnMapper.Obj2Table(field.Name), field.Name, sqlType, sqlType.DefaultLength, sqlType.DefaultLength2, true) + col.FieldIndex = []int{fieldIndex} if field.Type.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { col.IsAutoIncrement = true @@ -150,9 +151,10 @@ func (parser *Parser) parseFieldWithNoTag(field reflect.StructField, fieldValue return col, nil } -func (parser *Parser) parseFieldWithTags(table *schemas.Table, field reflect.StructField, fieldValue reflect.Value, tags []tag) (*schemas.Column, error) { +func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, field reflect.StructField, fieldValue reflect.Value, tags []tag) (*schemas.Column, error) { var col = &schemas.Column{ FieldName: field.Name, + FieldIndex: []int{fieldIndex}, Nullable: true, IsPrimaryKey: false, IsAutoIncrement: false, @@ -238,7 +240,7 @@ func (parser *Parser) parseFieldWithTags(table *schemas.Table, field reflect.Str return col, nil } -func (parser *Parser) parseField(table *schemas.Table, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { +func (parser *Parser) parseField(table *schemas.Table, fieldIndex int, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { var ( tag = field.Tag ormTagStr = strings.TrimSpace(tag.Get(parser.identifier)) @@ -247,13 +249,13 @@ func (parser *Parser) parseField(table *schemas.Table, field reflect.StructField return nil, ErrIgnoreField } if ormTagStr == "" { - return parser.parseFieldWithNoTag(field, fieldValue) + return parser.parseFieldWithNoTag(fieldIndex, field, fieldValue) } tags, err := splitTag(ormTagStr) if err != nil { return nil, err } - return parser.parseFieldWithTags(table, field, fieldValue, tags) + return parser.parseFieldWithTags(table, fieldIndex, field, fieldValue, tags) } func isNotTitle(n string) bool { @@ -279,16 +281,12 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table.Name = names.GetTableName(parser.tableMapper, v) for i := 0; i < t.NumField(); i++ { - if isNotTitle(t.Field(i).Name) { + var field = t.Field(i) + if isNotTitle(field.Name) { continue } - var ( - field = t.Field(i) - fieldValue = v.Field(i) - ) - - col, err := parser.parseField(table, field, fieldValue) + col, err := parser.parseField(table, i, field, v.Field(i)) if err == ErrIgnoreField { continue } else if err != nil { diff --git a/tags/tag.go b/tags/tag.go index d8d9bb46..4a39ba54 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -338,6 +338,7 @@ func ExtendsTagHandler(ctx *Context) error { } for _, col := range parentTable.Columns() { col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName) + col.FieldIndex = append(ctx.col.FieldIndex, col.FieldIndex...) var tagPrefix = ctx.col.FieldName if len(ctx.params) > 0 { From 65846bacc36a9f0f991d89998c1b399dd39e5d59 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 2 Jul 2021 11:25:20 +0800 Subject: [PATCH 062/179] Improve QueryString performance (#1962) As title. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1962 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- engine.go | 12 ++--- integrations/session_find_test.go | 15 +++--- integrations/session_insert_test.go | 30 +++++------ integrations/session_query_test.go | 8 +-- scan.go | 48 ++++++++++++++++++ session_query.go | 78 ++++++----------------------- 6 files changed, 95 insertions(+), 96 deletions(-) create mode 100644 scan.go diff --git a/engine.go b/engine.go index 76ce8f1a..0eb429b1 100644 --- a/engine.go +++ b/engine.go @@ -444,7 +444,7 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return engine.dumpTables(tables, w, tp...) } -func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas.Column) string { +func formatColumnValue(dbLocation *time.Location, dstDialect dialects.Dialect, d interface{}, col *schemas.Column) string { if d == nil { return "NULL" } @@ -473,10 +473,8 @@ func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas. return "'" + strings.Replace(v, "'", "''", -1) + "'" } else if col.SQLType.IsTime() { - if dstDialect.URI().DBType == schemas.MSSQL && col.SQLType.Name == schemas.DateTime { - if t, ok := d.(time.Time); ok { - return "'" + t.UTC().Format("2006-01-02 15:04:05") + "'" - } + if t, ok := d.(time.Time); ok { + return "'" + t.In(dbLocation).Format("2006-01-02 15:04:05") + "'" } var v = fmt.Sprintf("%s", d) if strings.HasSuffix(v, " +0000 UTC") { @@ -653,7 +651,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } field := dataStruct.FieldByIndex(col.FieldIndex) - temp += "," + formatColumnValue(dstDialect, field.Interface(), col) + temp += "," + formatColumnValue(engine.DatabaseTZ, dstDialect, field.Interface(), col) } _, err = io.WriteString(w, temp[1:]+");\n") if err != nil { @@ -680,7 +678,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return errors.New("unknow column error") } - temp += "," + formatColumnValue(dstDialect, d, col) + temp += "," + formatColumnValue(engine.DatabaseTZ, dstDialect, d, col) } _, err = io.WriteString(w, temp[1:]+");\n") if err != nil { diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 0ea12e26..80f3b72c 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -406,16 +406,16 @@ func TestFindMapPtrString(t *testing.T) { assert.NoError(t, err) } -func TestFindBit(t *testing.T) { - type FindBitStruct struct { +func TestFindBool(t *testing.T) { + type FindBoolStruct struct { Id int64 - Msg bool `xorm:"bit"` + Msg bool } assert.NoError(t, PrepareEngine()) - assertSync(t, new(FindBitStruct)) + assertSync(t, new(FindBoolStruct)) - cnt, err := testEngine.Insert([]FindBitStruct{ + cnt, err := testEngine.Insert([]FindBoolStruct{ { Msg: false, }, @@ -426,14 +426,13 @@ func TestFindBit(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, cnt) - var results = make([]FindBitStruct, 0, 2) + var results = make([]FindBoolStruct, 0, 2) err = testEngine.Find(&results) assert.NoError(t, err) assert.EqualValues(t, 2, len(results)) } func TestFindMark(t *testing.T) { - type Mark struct { Mark1 string `xorm:"VARCHAR(1)"` Mark2 string `xorm:"VARCHAR(1)"` @@ -468,7 +467,7 @@ func TestFindAndCountOneFunc(t *testing.T) { type FindAndCountStruct struct { Id int64 Content string - Msg bool `xorm:"bit"` + Msg bool } assert.NoError(t, PrepareEngine()) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index e5d880ae..72e9d050 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -168,17 +168,17 @@ func TestInsertAutoIncr(t *testing.T) { assert.Greater(t, user.Uid, int64(0)) } -type DefaultInsert struct { - Id int64 - Status int `xorm:"default -1"` - Name string - Created time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` -} - func TestInsertDefault(t *testing.T) { assert.NoError(t, PrepareEngine()) + type DefaultInsert struct { + Id int64 + Status int `xorm:"default -1"` + Name string + Created time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` + } + di := new(DefaultInsert) err := testEngine.Sync2(di) assert.NoError(t, err) @@ -195,16 +195,16 @@ func TestInsertDefault(t *testing.T) { assert.EqualValues(t, di2.Created.Unix(), di.Created.Unix()) } -type DefaultInsert2 struct { - Id int64 - Name string - Url string `xorm:"text"` - CheckTime time.Time `xorm:"not null default '2000-01-01 00:00:00' TIMESTAMP"` -} - func TestInsertDefault2(t *testing.T) { assert.NoError(t, PrepareEngine()) + type DefaultInsert2 struct { + Id int64 + Name string + Url string `xorm:"text"` + CheckTime time.Time `xorm:"not null default '2000-01-01 00:00:00' TIMESTAMP"` + } + di := new(DefaultInsert2) err := testEngine.Sync2(di) assert.NoError(t, err) diff --git a/integrations/session_query_test.go b/integrations/session_query_test.go index 30f2e6ab..5f3a0797 100644 --- a/integrations/session_query_test.go +++ b/integrations/session_query_test.go @@ -52,7 +52,7 @@ func TestQueryString2(t *testing.T) { type GetVar3 struct { Id int64 `xorm:"autoincr pk"` - Msg bool `xorm:"bit"` + Msg bool } assert.NoError(t, testEngine.Sync2(new(GetVar3))) @@ -192,7 +192,7 @@ func TestQueryStringNoParam(t *testing.T) { type GetVar4 struct { Id int64 `xorm:"autoincr pk"` - Msg bool `xorm:"bit"` + Msg bool } assert.NoError(t, testEngine.Sync2(new(GetVar4))) @@ -229,7 +229,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { type GetVar6 struct { Id int64 `xorm:"autoincr pk"` - Msg bool `xorm:"bit"` + Msg bool } assert.NoError(t, testEngine.Sync2(new(GetVar6))) @@ -266,7 +266,7 @@ func TestQueryInterfaceNoParam(t *testing.T) { type GetVar5 struct { Id int64 `xorm:"autoincr pk"` - Msg bool `xorm:"bit"` + Msg bool } assert.NoError(t, testEngine.Sync2(new(GetVar5))) diff --git a/scan.go b/scan.go new file mode 100644 index 00000000..0a9ef613 --- /dev/null +++ b/scan.go @@ -0,0 +1,48 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "database/sql" + + "xorm.io/xorm/core" +) + +func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { + var scanResults = make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var s sql.NullString + scanResults[i] = &s + } + + if err := rows.Scan(scanResults...); err != nil { + return nil, err + } + + result := make(map[string]string, len(fields)) + for ii, key := range fields { + s := scanResults[ii].(*sql.NullString) + result[key] = s.String + } + return result, nil +} + +func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { + results := make([]string, 0, len(fields)) + var scanResults = make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var s sql.NullString + scanResults[i] = &s + } + + if err := rows.Scan(scanResults...); err != nil { + return nil, err + } + + for i := 0; i < len(fields); i++ { + results = append(results, scanResults[i].(*sql.NullString).String) + } + return results, nil +} diff --git a/session_query.go b/session_query.go index 12136466..379ad0e1 100644 --- a/session_query.go +++ b/session_query.go @@ -75,69 +75,18 @@ func value2String(rawValue *reflect.Value) (str string, err error) { return } -func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { - result := make(map[string]string) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - // if row is null then as empty string - if rawValue.Interface() == nil { - result[key] = "" - continue - } - - if data, err := value2String(&rawValue); err == nil { - result[key] = data - } else { - return nil, err - } - } - return result, nil -} - -func row2sliceStr(rows *core.Rows, fields []string) (results []string, err error) { - result := make([]string, 0, len(fields)) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for i := 0; i < len(fields); i++ { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[i])) - // if row is null then as empty string - if rawValue.Interface() == nil { - result = append(result, "") - continue - } - - if data, err := value2String(&rawValue); err == nil { - result = append(result, data) - } else { - return nil, err - } - } - return result, nil -} - -func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { +func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { fields, err := rows.Columns() if err != nil { return nil, err } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + for rows.Next() { - result, err := row2mapStr(rows, fields) + result, err := session.engine.row2mapStr(rows, types, fields) if err != nil { return nil, err } @@ -147,13 +96,18 @@ func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) return resultsSlice, nil } -func rows2SliceString(rows *core.Rows) (resultsSlice [][]string, err error) { +func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]string, err error) { fields, err := rows.Columns() if err != nil { return nil, err } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + for rows.Next() { - record, err := row2sliceStr(rows, fields) + record, err := session.engine.row2sliceStr(rows, types, fields) if err != nil { return nil, err } @@ -180,7 +134,7 @@ func (session *Session) QueryString(sqlOrArgs ...interface{}) ([]map[string]stri } defer rows.Close() - return rows2Strings(rows) + return session.rows2Strings(rows) } // QuerySliceString runs a raw sql and return records as [][]string @@ -200,7 +154,7 @@ func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string, } defer rows.Close() - return rows2SliceString(rows) + return session.rows2SliceString(rows) } func row2mapInterface(rows *core.Rows, fields []string) (resultsMap map[string]interface{}, err error) { From 66fc59b71c87a1bf1ae27f0b8cb6315b1adf1d0b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 2 Jul 2021 12:37:03 +0800 Subject: [PATCH 063/179] Query bytes based on Query string (#1964) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1964 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- scan.go | 19 +++++++++++++++++++ session_raw.go | 38 +++++++------------------------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/scan.go b/scan.go index 0a9ef613..e19037a0 100644 --- a/scan.go +++ b/scan.go @@ -29,6 +29,25 @@ func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, field return result, nil } +func (engine *Engine) row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) { + var scanResults = make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var s sql.NullString + scanResults[i] = &s + } + + if err := rows.Scan(scanResults...); err != nil { + return nil, err + } + + result := make(map[string][]byte, len(fields)) + for ii, key := range fields { + s := scanResults[ii].(*sql.NullString) + result[key] = []byte(s.String) + } + return result, nil +} + func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { results := make([]string, 0, len(fields)) var scanResults = make([]interface{}, len(fields)) diff --git a/session_raw.go b/session_raw.go index 4cfe297a..d5c4520b 100644 --- a/session_raw.go +++ b/session_raw.go @@ -79,41 +79,17 @@ func value2Bytes(rawValue *reflect.Value) ([]byte, error) { return []byte(str), nil } -func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { - result := make(map[string][]byte) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - //if row is null then ignore - if rawValue.Interface() == nil { - result[key] = []byte{} - continue - } - - if data, err := value2Bytes(&rawValue); err == nil { - result[key] = data - } else { - return nil, err // !nashtsai! REVIEW, should return err or just error log? - } - } - return result, nil -} - -func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { +func (session *Session) rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { fields, err := rows.Columns() if err != nil { return nil, err } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } for rows.Next() { - result, err := row2map(rows, fields) + result, err := session.engine.row2mapBytes(rows, types, fields) if err != nil { return nil, err } @@ -130,7 +106,7 @@ func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[st } defer rows.Close() - return rows2maps(rows) + return session.rows2maps(rows) } func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { From 962962bb64af2d2f9fd3964794d425ceaa83ea9d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 3 Jul 2021 20:26:49 +0800 Subject: [PATCH 064/179] Fix #929 (#1936) sql server doesn't accept to insert a blank datetime like `0001-01-01 00:00:00`. So that we have a break change here that deleted column should not have a notnull tag. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1936 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_insert_test.go | 41 +++++++++++++++++++++++++++++ internal/statements/insert.go | 2 +- internal/statements/statement.go | 2 +- session_insert.go | 11 ++++++++ tags/parser.go | 6 +++++ tags/tag.go | 1 + 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 72e9d050..a023ab72 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -1024,3 +1024,44 @@ func TestInsertIntSlice(t *testing.T) { assert.True(t, has) assert.EqualValues(t, v3, v4) } + +func TestInsertDeleted(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type InsertDeletedStructNotRight struct { + ID uint64 `xorm:"'ID' pk autoincr"` + DeletedAt time.Time `xorm:"'DELETED_AT' deleted notnull"` + } + // notnull tag will be ignored + err := testEngine.Sync2(new(InsertDeletedStructNotRight)) + assert.NoError(t, err) + + type InsertDeletedStruct struct { + ID uint64 `xorm:"'ID' pk autoincr"` + DeletedAt time.Time `xorm:"'DELETED_AT' deleted"` + } + + assert.NoError(t, testEngine.Sync2(new(InsertDeletedStruct))) + + var v InsertDeletedStruct + _, err = testEngine.Insert(&v) + assert.NoError(t, err) + + var v2 InsertDeletedStruct + has, err := testEngine.Get(&v2) + assert.NoError(t, err) + assert.True(t, has) + + _, err = testEngine.ID(v.ID).Delete(new(InsertDeletedStruct)) + assert.NoError(t, err) + + var v3 InsertDeletedStruct + has, err = testEngine.Get(&v3) + assert.NoError(t, err) + assert.False(t, has) + + var v4 InsertDeletedStruct + has, err = testEngine.Unscoped().Get(&v4) + assert.NoError(t, err) + assert.True(t, has) +} diff --git a/internal/statements/insert.go b/internal/statements/insert.go index 367dbdc9..4e43c5bd 100644 --- a/internal/statements/insert.go +++ b/internal/statements/insert.go @@ -17,7 +17,7 @@ func (statement *Statement) writeInsertOutput(buf *strings.Builder, table *schem if _, err := buf.WriteString(" OUTPUT Inserted."); err != nil { return err } - if _, err := buf.WriteString(table.AutoIncrement); err != nil { + if err := statement.dialect.Quoter().QuoteTo(buf, table.AutoIncrement); err != nil { return err } } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index b1a5ed3c..2d173b87 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -978,7 +978,7 @@ func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName // CondDeleted returns the conditions whether a record is soft deleted. func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { - var colName = col.Name + var colName = statement.quote(col.Name) if statement.JoinStr != "" { var prefix string if statement.TableAlias != "" { diff --git a/session_insert.go b/session_insert.go index 82d91969..e733e06e 100644 --- a/session_insert.go +++ b/session_insert.go @@ -11,6 +11,7 @@ import ( "sort" "strconv" "strings" + "time" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" @@ -497,6 +498,16 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac } if col.IsDeleted { + colNames = append(colNames, col.Name) + if !col.Nullable { + if col.SQLType.IsNumeric() { + args = append(args, 0) + } else { + args = append(args, time.Time{}.Format("2006-01-02 15:04:05")) + } + } else { + args = append(args, nil) + } continue } diff --git a/tags/parser.go b/tags/parser.go index d701e316..b793a8f1 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -296,5 +296,11 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table.AddColumn(col) } // end for + deletedColumn := table.DeletedColumn() + // check columns + if deletedColumn != nil { + deletedColumn.Nullable = true + } + return table, nil } diff --git a/tags/tag.go b/tags/tag.go index 4a39ba54..641b8c52 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -238,6 +238,7 @@ func UpdatedTagHandler(ctx *Context) error { // DeletedTagHandler describes deleted tag handler func DeletedTagHandler(ctx *Context) error { ctx.col.IsDeleted = true + ctx.col.Nullable = true return nil } From 60e128eb4d92027de3a99b59fb1f4c5751eac382 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 3 Jul 2021 22:45:28 +0800 Subject: [PATCH 065/179] Changelog for v1.1.1 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e721ec..ce5b4fe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,36 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.1.1](https://gitea.com/xorm/xorm/releases/tag/1.1.1) - 2021-07-03 + +* BUGFIXES + * Ignore comments when deciding when to replace question marks. #1954 (#1955) + * Fix bug didn't reset statement on update (#1939) + * Fix create table with struct missing columns (#1938) + * Fix #929 (#1936) + * Fix exist (#1921) +* ENHANCEMENTS + * Improve get field value of bean (#1961) + * refactor splitTag function (#1960) + * Fix #1663 (#1952) + * fix pg GetColumns missing comment (#1949) + * Support build flag jsoniter to replace default json (#1916) + * refactor exprParam (#1825) + * Add DBVersion (#1723) +* TESTING + * Add test to confirm #1247 resolved (#1951) + * Add test for dump table with default value (#1950) + * Test for #1486 (#1942) + * Add sync tests to confirm #539 is gone (#1937) + * test for unsigned int32 (#1923) + * Add tests for array store (#1922) +* BUILD + * Remove mymysql from ci (#1928) +* MISC + * fix lint (#1953) + * Compitable with cockroach (#1930) + * Replace goracle with godror (#1914) + ## [1.1.0](https://gitea.com/xorm/xorm/releases/tag/1.1.0) - 2021-05-14 * FEATURES From cbc40dfe5c36c63bd93d567e8cafc6d77d7b9dc5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 4 Jul 2021 18:19:46 +0800 Subject: [PATCH 066/179] Add release tag (#1966) as title Reviewed-on: https://gitea.com/xorm/xorm/pulls/1966 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.drone.yml b/.drone.yml index 4f84d7fa..8a9f8877 100644 --- a/.drone.yml +++ b/.drone.yml @@ -347,3 +347,19 @@ steps: image: golang:1.15 commands: - make coverage + +--- +kind: pipeline +name: release-tag +trigger: + event: + - tag +steps: +- name: release-tag-gitea + pull: always + image: plugins/gitea-release:latest + settings: + base_url: https://gitea.com + title: '${DRONE_TAG} is released' + api_key: + from_secret: gitea_token \ No newline at end of file From 4f92921e43a27093034284b6cf0109c1b214a591 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 4 Jul 2021 19:04:48 +0800 Subject: [PATCH 067/179] Add changelog for v1.1.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5b4fe9..cd567b27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.1.2](https://gitea.com/xorm/xorm/releases/tag/1.1.2) - 2021-07-04 + +* BUILD + * Add release tag (#1966) + ## [1.1.1](https://gitea.com/xorm/xorm/releases/tag/1.1.1) - 2021-07-03 * BUGFIXES From d0e5dba40efff87a4e58c827ba0e4276b518539d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 4 Jul 2021 21:23:17 +0800 Subject: [PATCH 068/179] Query interface (#1965) refactor query interface Reviewed-on: https://gitea.com/xorm/xorm/pulls/1965 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert/interface.go | 48 +++++++++ convert/time.go | 30 ++++++ dialects/driver.go | 18 ++++ dialects/mssql.go | 25 +++++ dialects/mysql.go | 157 ++++++++++++++++++++++------- dialects/oracle.go | 23 +++++ dialects/postgres.go | 32 ++++++ dialects/sqlite3.go | 27 +++++ engine.go | 2 + integrations/session_query_test.go | 26 +++-- scan.go | 55 +++++++++- session_query.go | 84 ++------------- session_raw.go | 73 ++++++++++---- 13 files changed, 455 insertions(+), 145 deletions(-) create mode 100644 convert/interface.go create mode 100644 convert/time.go diff --git a/convert/interface.go b/convert/interface.go new file mode 100644 index 00000000..2b055253 --- /dev/null +++ b/convert/interface.go @@ -0,0 +1,48 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "database/sql" + "fmt" + "time" +) + +func Interface2Interface(userLocation *time.Location, v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + switch vv := v.(type) { + case *int64: + return *vv, nil + case *int8: + return *vv, nil + case *sql.NullString: + return vv.String, nil + case *sql.RawBytes: + if len([]byte(*vv)) > 0 { + return []byte(*vv), nil + } + return nil, nil + case *sql.NullInt32: + return vv.Int32, nil + case *sql.NullInt64: + return vv.Int64, nil + case *sql.NullFloat64: + return vv.Float64, nil + case *sql.NullBool: + if vv.Valid { + return vv.Bool, nil + } + return nil, nil + case *sql.NullTime: + if vv.Valid { + return vv.Time.In(userLocation).Format("2006-01-02 15:04:05"), nil + } + return "", nil + default: + return "", fmt.Errorf("convert assign string unsupported type: %#v", vv) + } +} diff --git a/convert/time.go b/convert/time.go new file mode 100644 index 00000000..8901279b --- /dev/null +++ b/convert/time.go @@ -0,0 +1,30 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "fmt" + "time" +) + +// String2Time converts a string to time with original location +func String2Time(s string, originalLocation *time.Location, convertedLocation *time.Location) (*time.Time, error) { + if len(s) == 19 { + dt, err := time.ParseInLocation("2006-01-02 15:04:05", s, originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil + } else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' { + dt, err := time.ParseInLocation("2006-01-02T15:04:05Z", s, originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil + } + return nil, fmt.Errorf("unsupported convertion from %s to time", s) +} diff --git a/dialects/driver.go b/dialects/driver.go index bb46a936..c511b665 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -5,12 +5,24 @@ package dialects import ( + "database/sql" "fmt" + "time" + + "xorm.io/xorm/core" ) +// ScanContext represents a context when Scan +type ScanContext struct { + DBLocation *time.Location + UserLocation *time.Location +} + // Driver represents a database driver type Driver interface { Parse(string, string) (*URI, error) + GenScanResult(string) (interface{}, error) // according given column type generating a suitable scan interface + Scan(*ScanContext, *core.Rows, []*sql.ColumnType, ...interface{}) error } var ( @@ -59,3 +71,9 @@ func OpenDialect(driverName, connstr string) (Dialect, error) { return dialect, nil } + +type baseDriver struct{} + +func (b *baseDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, v ...interface{}) error { + return rows.Scan(v...) +} diff --git a/dialects/mssql.go b/dialects/mssql.go index 7e922e62..c3c15077 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -6,6 +6,7 @@ package dialects import ( "context" + "database/sql" "errors" "fmt" "net/url" @@ -624,6 +625,7 @@ func (db *mssql) Filters() []Filter { } type odbcDriver struct { + baseDriver } func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) { @@ -652,3 +654,26 @@ func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) { } return &URI{DBName: dbName, DBType: schemas.MSSQL}, nil } + +func (p *odbcDriver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "VARCHAR", "TEXT", "CHAR", "NVARCHAR", "NCHAR", "NTEXT": + fallthrough + case "DATE", "DATETIME", "DATETIME2", "TIME": + var s sql.NullString + return &s, nil + case "FLOAT", "REAL": + var s sql.NullFloat64 + return &s, nil + case "BIGINT", "DATETIMEOFFSET": + var s sql.NullInt64 + return &s, nil + case "TINYINT", "SMALLINT", "INT": + var s sql.NullInt32 + return &s, nil + + default: + var r sql.RawBytes + return &r, nil + } +} diff --git a/dialects/mysql.go b/dialects/mysql.go index a169b901..03bc9a4b 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -7,6 +7,7 @@ package dialects import ( "context" "crypto/tls" + "database/sql" "errors" "fmt" "regexp" @@ -14,6 +15,7 @@ import ( "strings" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/schemas" ) @@ -630,7 +632,124 @@ func (db *mysql) Filters() []Filter { return []Filter{} } +type mysqlDriver struct { +} + +func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { + dsnPattern := regexp.MustCompile( + `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] + `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] + `\/(?P.*?)` + // /dbname + `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] + matches := dsnPattern.FindStringSubmatch(dataSourceName) + // tlsConfigRegister := make(map[string]*tls.Config) + names := dsnPattern.SubexpNames() + + uri := &URI{DBType: schemas.MYSQL} + + for i, match := range matches { + switch names[i] { + case "dbname": + uri.DBName = match + case "params": + if len(match) > 0 { + kvs := strings.Split(match, "&") + for _, kv := range kvs { + splits := strings.Split(kv, "=") + if len(splits) == 2 { + switch splits[0] { + case "charset": + uri.Charset = splits[1] + } + } + } + } + + } + } + return uri, nil +} + +func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET": + var s sql.NullString + return &s, nil + case "BIGINT": + var s sql.NullInt64 + return &s, nil + case "TINYINT", "SMALLINT", "MEDIUMINT", "INT": + var s sql.NullInt32 + return &s, nil + case "FLOAT", "REAL", "DOUBLE PRECISION": + var s sql.NullFloat64 + return &s, nil + case "DECIMAL", "NUMERIC": + var s sql.NullString + return &s, nil + case "DATETIME": + var s sql.NullTime + return &s, nil + case "BIT": + var s sql.RawBytes + return &s, nil + case "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB": + var r sql.RawBytes + return &r, nil + default: + var r sql.RawBytes + return &r, nil + } +} + +func (p *mysqlDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, scanResults ...interface{}) error { + var v2 = make([]interface{}, 0, len(scanResults)) + var turnBackIdxes = make([]int, 0, 5) + for i, vv := range scanResults { + switch vv.(type) { + case *time.Time: + v2 = append(v2, &sql.NullString{}) + turnBackIdxes = append(turnBackIdxes, i) + case *sql.NullTime: + v2 = append(v2, &sql.NullString{}) + turnBackIdxes = append(turnBackIdxes, i) + default: + v2 = append(v2, scanResults[i]) + } + } + if err := rows.Scan(v2...); err != nil { + return err + } + for _, i := range turnBackIdxes { + switch t := scanResults[i].(type) { + case *time.Time: + var s = *(v2[i].(*sql.NullString)) + if !s.Valid { + break + } + dt, err := convert.String2Time(s.String, ctx.DBLocation, ctx.UserLocation) + if err != nil { + return err + } + *t = *dt + case *sql.NullTime: + var s = *(v2[i].(*sql.NullString)) + if !s.Valid { + break + } + dt, err := convert.String2Time(s.String, ctx.DBLocation, ctx.UserLocation) + if err != nil { + return err + } + t.Time = *dt + t.Valid = true + } + } + return nil +} + type mymysqlDriver struct { + mysqlDriver } func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { @@ -681,41 +800,3 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { return uri, nil } - -type mysqlDriver struct { -} - -func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { - dsnPattern := regexp.MustCompile( - `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] - `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] - `\/(?P.*?)` + // /dbname - `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] - matches := dsnPattern.FindStringSubmatch(dataSourceName) - // tlsConfigRegister := make(map[string]*tls.Config) - names := dsnPattern.SubexpNames() - - uri := &URI{DBType: schemas.MYSQL} - - for i, match := range matches { - switch names[i] { - case "dbname": - uri.DBName = match - case "params": - if len(match) > 0 { - kvs := strings.Split(match, "&") - for _, kv := range kvs { - splits := strings.Split(kv, "=") - if len(splits) == 2 { - switch splits[0] { - case "charset": - uri.Charset = splits[1] - } - } - } - } - - } - } - return uri, nil -} diff --git a/dialects/oracle.go b/dialects/oracle.go index 0b06c4c6..7043972b 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -6,6 +6,7 @@ package dialects import ( "context" + "database/sql" "errors" "fmt" "regexp" @@ -823,6 +824,7 @@ func (db *oracle) Filters() []Filter { } type godrorDriver struct { + baseDriver } func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) { @@ -848,7 +850,28 @@ func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) return db, nil } +func (p *godrorDriver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": + var s sql.NullString + return &s, nil + case "NUMBER": + var s sql.NullString + return &s, nil + case "DATE": + var s sql.NullTime + return &s, nil + case "BLOB": + var r sql.RawBytes + return &r, nil + default: + var r sql.RawBytes + return &r, nil + } +} + type oci8Driver struct { + godrorDriver } // dataSourceName=user/password@ipv4:port/dbname diff --git a/dialects/postgres.go b/dialects/postgres.go index 9acf763a..e4641509 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -6,6 +6,7 @@ package dialects import ( "context" + "database/sql" "errors" "fmt" "net/url" @@ -1298,6 +1299,7 @@ func (db *postgres) Filters() []Filter { } type pqDriver struct { + baseDriver } type values map[string]string @@ -1374,6 +1376,36 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*URI, error) { return db, nil } +func (p *pqDriver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "VARCHAR", "TEXT": + var s sql.NullString + return &s, nil + case "BIGINT": + var s sql.NullInt64 + return &s, nil + case "TINYINT", "INT", "INT8", "INT4": + var s sql.NullInt32 + return &s, nil + case "FLOAT", "FLOAT4": + var s sql.NullFloat64 + return &s, nil + case "DATETIME", "TIMESTAMP": + var s sql.NullTime + return &s, nil + case "BIT": + var s sql.RawBytes + return &s, nil + case "BOOL": + var s sql.NullBool + return &s, nil + default: + fmt.Printf("unknow postgres database type: %v\n", colType) + var r sql.RawBytes + return &r, nil + } +} + type pqDriverPgx struct { pqDriver } diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index a42aad48..306f377c 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -540,6 +540,7 @@ func (db *sqlite3) Filters() []Filter { } type sqlite3Driver struct { + baseDriver } func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) { @@ -549,3 +550,29 @@ func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) { return &URI{DBType: schemas.SQLITE, DBName: dataSourceName}, nil } + +func (p *sqlite3Driver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "TEXT": + var s sql.NullString + return &s, nil + case "INTEGER": + var s sql.NullInt64 + return &s, nil + case "DATETIME": + var s sql.NullTime + return &s, nil + case "REAL": + var s sql.NullFloat64 + return &s, nil + case "NUMERIC": + var s sql.NullString + return &s, nil + case "BLOB": + var s sql.RawBytes + return &s, nil + default: + var r sql.NullString + return &r, nil + } +} diff --git a/engine.go b/engine.go index 0eb429b1..1064e8e1 100644 --- a/engine.go +++ b/engine.go @@ -35,6 +35,7 @@ type Engine struct { cacherMgr *caches.Manager defaultContext context.Context dialect dialects.Dialect + driver dialects.Driver engineGroup *EngineGroup logger log.ContextLogger tagParser *tags.Parser @@ -72,6 +73,7 @@ func newEngine(driverName, dataSourceName string, dialect dialects.Dialect, db * engine := &Engine{ dialect: dialect, + driver: dialects.QueryDriver(driverName), TZLocation: time.Local, defaultContext: context.Background(), cacherMgr: cacherMgr, diff --git a/integrations/session_query_test.go b/integrations/session_query_test.go index 5f3a0797..ed03ff3e 100644 --- a/integrations/session_query_test.go +++ b/integrations/session_query_test.go @@ -107,6 +107,16 @@ func toFloat64(i interface{}) float64 { return 0 } +func toBool(i interface{}) bool { + switch t := i.(type) { + case int32: + return t > 0 + case bool: + return t + } + return false +} + func TestQueryInterface(t *testing.T) { assert.NoError(t, PrepareEngine()) @@ -132,10 +142,10 @@ func TestQueryInterface(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, len(records)) assert.Equal(t, 5, len(records[0])) - assert.EqualValues(t, 1, toInt64(records[0]["id"])) - assert.Equal(t, "hi", toString(records[0]["msg"])) - assert.EqualValues(t, 28, toInt64(records[0]["age"])) - assert.EqualValues(t, 1.5, toFloat64(records[0]["money"])) + assert.EqualValues(t, int64(1), records[0]["id"]) + assert.Equal(t, "hi", records[0]["msg"]) + assert.EqualValues(t, 28, records[0]["age"]) + assert.EqualValues(t, 1.5, records[0]["money"]) } func TestQueryNoParams(t *testing.T) { @@ -280,14 +290,14 @@ func TestQueryInterfaceNoParam(t *testing.T) { records, err := testEngine.Table("get_var5").Limit(1).QueryInterface() assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) - assert.EqualValues(t, 1, toInt64(records[0]["id"])) - assert.EqualValues(t, 0, toInt64(records[0]["msg"])) + assert.EqualValues(t, 1, records[0]["id"]) + assert.False(t, toBool(records[0]["msg"])) records, err = testEngine.Table("get_var5").Where(builder.Eq{"id": 1}).QueryInterface() assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) - assert.EqualValues(t, 1, toInt64(records[0]["id"])) - assert.EqualValues(t, 0, toInt64(records[0]["msg"])) + assert.EqualValues(t, 1, records[0]["id"]) + assert.False(t, toBool(records[0]["msg"])) } func TestQueryWithBuilder(t *testing.T) { diff --git a/scan.go b/scan.go index e19037a0..e11d6e8d 100644 --- a/scan.go +++ b/scan.go @@ -7,10 +7,12 @@ package xorm import ( "database/sql" + "xorm.io/xorm/convert" "xorm.io/xorm/core" + "xorm.io/xorm/dialects" ) -func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { +func row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { var scanResults = make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { var s sql.NullString @@ -29,7 +31,7 @@ func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, field return result, nil } -func (engine *Engine) row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) { +func row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) { var scanResults = make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { var s sql.NullString @@ -48,7 +50,7 @@ func (engine *Engine) row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fie return result, nil } -func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { +func row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { results := make([]string, 0, len(fields)) var scanResults = make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { @@ -65,3 +67,50 @@ func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fie } return results, nil } + +func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2mapBytes(rows, types, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + + return resultsSlice, nil +} + +func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]interface{}, error) { + var resultsMap = make(map[string]interface{}, len(fields)) + var scanResultContainers = make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName()) + if err != nil { + return nil, err + } + scanResultContainers[i] = scanResult + } + if err := engine.driver.Scan(&dialects.ScanContext{ + DBLocation: engine.DatabaseTZ, + UserLocation: engine.TZLocation, + }, rows, types, scanResultContainers...); err != nil { + return nil, err + } + + for ii, key := range fields { + res, err := convert.Interface2Interface(engine.TZLocation, scanResultContainers[ii]) + if err != nil { + return nil, err + } + resultsMap[key] = res + } + return resultsMap, nil +} diff --git a/session_query.go b/session_query.go index 379ad0e1..01cd6f44 100644 --- a/session_query.go +++ b/session_query.go @@ -5,13 +5,7 @@ package xorm import ( - "fmt" - "reflect" - "strconv" - "time" - "xorm.io/xorm/core" - "xorm.io/xorm/schemas" ) // Query runs a raw sql and return records as []map[string][]byte @@ -28,53 +22,6 @@ func (session *Session) Query(sqlOrArgs ...interface{}) ([]map[string][]byte, er return session.queryBytes(sqlStr, args...) } -func value2String(rawValue *reflect.Value) (str string, err error) { - aa := reflect.TypeOf((*rawValue).Interface()) - vv := reflect.ValueOf((*rawValue).Interface()) - switch aa.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - str = strconv.FormatInt(vv.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - str = strconv.FormatUint(vv.Uint(), 10) - case reflect.Float32, reflect.Float64: - str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) - case reflect.String: - str = vv.String() - case reflect.Array, reflect.Slice: - switch aa.Elem().Kind() { - case reflect.Uint8: - data := rawValue.Interface().([]byte) - str = string(data) - if str == "\x00" { - str = "0" - } - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - // time type - case reflect.Struct: - if aa.ConvertibleTo(schemas.TimeType) { - str = vv.Convert(schemas.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) - } else { - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - case reflect.Bool: - str = strconv.FormatBool(vv.Bool()) - case reflect.Complex128, reflect.Complex64: - str = fmt.Sprintf("%v", vv.Complex()) - /* TODO: unsupported types below - case reflect.Map: - case reflect.Ptr: - case reflect.Uintptr: - case reflect.UnsafePointer: - case reflect.Chan, reflect.Func, reflect.Interface: - */ - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - return -} - func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { fields, err := rows.Columns() if err != nil { @@ -86,7 +33,7 @@ func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string } for rows.Next() { - result, err := session.engine.row2mapStr(rows, types, fields) + result, err := row2mapStr(rows, types, fields) if err != nil { return nil, err } @@ -107,7 +54,7 @@ func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]stri } for rows.Next() { - record, err := session.engine.row2sliceStr(rows, types, fields) + record, err := row2sliceStr(rows, types, fields) if err != nil { return nil, err } @@ -157,30 +104,17 @@ func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string, return session.rows2SliceString(rows) } -func row2mapInterface(rows *core.Rows, fields []string) (resultsMap map[string]interface{}, err error) { - resultsMap = make(map[string]interface{}, len(fields)) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - resultsMap[key] = reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])).Interface() - } - return -} - -func rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) { +func (session *Session) rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) { fields, err := rows.Columns() if err != nil { return nil, err } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } for rows.Next() { - result, err := row2mapInterface(rows, fields) + result, err := session.engine.row2mapInterface(rows, types, fields) if err != nil { return nil, err } @@ -207,5 +141,5 @@ func (session *Session) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]i } defer rows.Close() - return rows2Interfaces(rows) + return session.rows2Interfaces(rows) } diff --git a/session_raw.go b/session_raw.go index d5c4520b..bf32c6ed 100644 --- a/session_raw.go +++ b/session_raw.go @@ -6,9 +6,13 @@ package xorm import ( "database/sql" + "fmt" "reflect" + "strconv" + "time" "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { @@ -71,6 +75,53 @@ func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row { return core.NewRow(session.queryRows(sqlStr, args...)) } +func value2String(rawValue *reflect.Value) (str string, err error) { + aa := reflect.TypeOf((*rawValue).Interface()) + vv := reflect.ValueOf((*rawValue).Interface()) + switch aa.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + str = strconv.FormatInt(vv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + str = strconv.FormatUint(vv.Uint(), 10) + case reflect.Float32, reflect.Float64: + str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) + case reflect.String: + str = vv.String() + case reflect.Array, reflect.Slice: + switch aa.Elem().Kind() { + case reflect.Uint8: + data := rawValue.Interface().([]byte) + str = string(data) + if str == "\x00" { + str = "0" + } + default: + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + // time type + case reflect.Struct: + if aa.ConvertibleTo(schemas.TimeType) { + str = vv.Convert(schemas.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) + } else { + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + case reflect.Bool: + str = strconv.FormatBool(vv.Bool()) + case reflect.Complex128, reflect.Complex64: + str = fmt.Sprintf("%v", vv.Complex()) + /* TODO: unsupported types below + case reflect.Map: + case reflect.Ptr: + case reflect.Uintptr: + case reflect.UnsafePointer: + case reflect.Chan, reflect.Func, reflect.Interface: + */ + default: + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + return +} + func value2Bytes(rawValue *reflect.Value) ([]byte, error) { str, err := value2String(rawValue) if err != nil { @@ -79,26 +130,6 @@ func value2Bytes(rawValue *reflect.Value) ([]byte, error) { return []byte(str), nil } -func (session *Session) rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - types, err := rows.ColumnTypes() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := session.engine.row2mapBytes(rows, types, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - - return resultsSlice, nil -} - func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[string][]byte, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { @@ -106,7 +137,7 @@ func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[st } defer rows.Close() - return session.rows2maps(rows) + return rows2maps(rows) } func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { From a5030dc7a444c098b8c97d514f06df90ed6b57f8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 6 Jul 2021 16:06:04 +0800 Subject: [PATCH 069/179] refactor get (#1967) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1967 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 416 +++++++++++++++++++++++++++++++++++++++++-- dialects/driver.go | 11 ++ dialects/mysql.go | 1 + dialects/postgres.go | 6 + dialects/sqlite3.go | 6 + scan.go | 197 +++++++++++++++++++- session_find.go | 2 +- session_get.go | 299 ++++++++++++++++--------------- session_insert.go | 6 +- session_query.go | 2 +- 10 files changed, 784 insertions(+), 162 deletions(-) diff --git a/convert.go b/convert.go index b7f30cad..f7d733ad 100644 --- a/convert.go +++ b/convert.go @@ -5,12 +5,15 @@ package xorm import ( + "database/sql" "database/sql/driver" "errors" "fmt" "reflect" "strconv" "time" + + "xorm.io/xorm/convert" ) var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error @@ -37,6 +40,12 @@ func asString(src interface{}) string { return v case []byte: return string(v) + case *sql.NullString: + return v.String + case *sql.NullInt32: + return fmt.Sprintf("%d", v.Int32) + case *sql.NullInt64: + return fmt.Sprintf("%d", v.Int64) } rv := reflect.ValueOf(src) switch rv.Kind() { @@ -54,6 +63,156 @@ func asString(src interface{}) string { return fmt.Sprintf("%v", src) } +func asInt64(src interface{}) (int64, error) { + switch v := src.(type) { + case int: + return int64(v), nil + case int16: + return int64(v), nil + case int32: + return int64(v), nil + case int8: + return int64(v), nil + case int64: + return v, nil + case uint: + return int64(v), nil + case uint8: + return int64(v), nil + case uint16: + return int64(v), nil + case uint32: + return int64(v), nil + case uint64: + return int64(v), nil + case []byte: + return strconv.ParseInt(string(v), 10, 64) + case string: + return strconv.ParseInt(v, 10, 64) + case *sql.NullString: + return strconv.ParseInt(v.String, 10, 64) + case *sql.NullInt32: + return int64(v.Int32), nil + case *sql.NullInt64: + return int64(v.Int64), nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int64(rv.Uint()), nil + case reflect.Float64: + return int64(rv.Float()), nil + case reflect.Float32: + return int64(rv.Float()), nil + case reflect.String: + return strconv.ParseInt(rv.String(), 10, 64) + } + return 0, fmt.Errorf("unsupported value %T as int64", src) +} + +func asUint64(src interface{}) (uint64, error) { + switch v := src.(type) { + case int: + return uint64(v), nil + case int16: + return uint64(v), nil + case int32: + return uint64(v), nil + case int8: + return uint64(v), nil + case int64: + return uint64(v), nil + case uint: + return uint64(v), nil + case uint8: + return uint64(v), nil + case uint16: + return uint64(v), nil + case uint32: + return uint64(v), nil + case uint64: + return v, nil + case []byte: + return strconv.ParseUint(string(v), 10, 64) + case string: + return strconv.ParseUint(v, 10, 64) + case *sql.NullString: + return strconv.ParseUint(v.String, 10, 64) + case *sql.NullInt32: + return uint64(v.Int32), nil + case *sql.NullInt64: + return uint64(v.Int64), nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return uint64(rv.Uint()), nil + case reflect.Float64: + return uint64(rv.Float()), nil + case reflect.Float32: + return uint64(rv.Float()), nil + case reflect.String: + return strconv.ParseUint(rv.String(), 10, 64) + } + return 0, fmt.Errorf("unsupported value %T as uint64", src) +} + +func asFloat64(src interface{}) (float64, error) { + switch v := src.(type) { + case int: + return float64(v), nil + case int16: + return float64(v), nil + case int32: + return float64(v), nil + case int8: + return float64(v), nil + case int64: + return float64(v), nil + case uint: + return float64(v), nil + case uint8: + return float64(v), nil + case uint16: + return float64(v), nil + case uint32: + return float64(v), nil + case uint64: + return float64(v), nil + case []byte: + return strconv.ParseFloat(string(v), 64) + case string: + return strconv.ParseFloat(v, 64) + case *sql.NullString: + return strconv.ParseFloat(v.String, 64) + case *sql.NullInt32: + return float64(v.Int32), nil + case *sql.NullInt64: + return float64(v.Int64), nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(rv.Uint()), nil + case reflect.Float64: + return float64(rv.Float()), nil + case reflect.Float32: + return float64(rv.Float()), nil + case reflect.String: + return strconv.ParseFloat(rv.String(), 64) + } + return 0, fmt.Errorf("unsupported value %T as int64", src) +} + func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -76,7 +235,7 @@ func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { // convertAssign copies to dest the value in src, converting it if possible. // An error is returned if the copy would result in loss of information. // dest should be a pointer type. -func convertAssign(dest, src interface{}) error { +func convertAssign(dest, src interface{}, originalLocation *time.Location, convertedLocation *time.Location) error { // Common cases, without reflect. switch s := src.(type) { case string: @@ -143,6 +302,163 @@ func convertAssign(dest, src interface{}) error { *d = nil return nil } + case *sql.NullString: + switch d := dest.(type) { + case *int: + if s.Valid { + *d, _ = strconv.Atoi(s.String) + } + case *int64: + if s.Valid { + *d, _ = strconv.ParseInt(s.String, 10, 64) + } + case *string: + if s.Valid { + *d = s.String + } + return nil + case *time.Time: + if s.Valid { + var err error + dt, err := convert.String2Time(s.String, originalLocation, convertedLocation) + if err != nil { + return err + } + *d = *dt + } + return nil + case *sql.NullTime: + if s.Valid { + var err error + dt, err := convert.String2Time(s.String, originalLocation, convertedLocation) + if err != nil { + return err + } + d.Valid = true + d.Time = *dt + } + } + case *sql.NullInt32: + switch d := dest.(type) { + case *int: + if s.Valid { + *d = int(s.Int32) + } + return nil + case *int8: + if s.Valid { + *d = int8(s.Int32) + } + return nil + case *int16: + if s.Valid { + *d = int16(s.Int32) + } + return nil + case *int32: + if s.Valid { + *d = s.Int32 + } + return nil + case *int64: + if s.Valid { + *d = int64(s.Int32) + } + return nil + } + case *sql.NullInt64: + switch d := dest.(type) { + case *int: + if s.Valid { + *d = int(s.Int64) + } + return nil + case *int8: + if s.Valid { + *d = int8(s.Int64) + } + return nil + case *int16: + if s.Valid { + *d = int16(s.Int64) + } + return nil + case *int32: + if s.Valid { + *d = int32(s.Int64) + } + return nil + case *int64: + if s.Valid { + *d = s.Int64 + } + return nil + } + case *sql.NullFloat64: + switch d := dest.(type) { + case *int: + if s.Valid { + *d = int(s.Float64) + } + return nil + case *float64: + if s.Valid { + *d = s.Float64 + } + return nil + } + case *sql.NullBool: + switch d := dest.(type) { + case *bool: + if s.Valid { + *d = s.Bool + } + return nil + } + case *sql.NullTime: + switch d := dest.(type) { + case *time.Time: + if s.Valid { + *d = s.Time + } + return nil + case *string: + if s.Valid { + *d = s.Time.In(convertedLocation).Format("2006-01-02 15:04:05") + } + return nil + } + case *NullUint32: + switch d := dest.(type) { + case *uint8: + if s.Valid { + *d = uint8(s.Uint32) + } + return nil + case *uint16: + if s.Valid { + *d = uint16(s.Uint32) + } + return nil + case *uint: + if s.Valid { + *d = uint(s.Uint32) + } + return nil + } + case *NullUint64: + switch d := dest.(type) { + case *uint64: + if s.Valid { + *d = s.Uint64 + } + return nil + } + case *sql.RawBytes: + switch d := dest.(type) { + case convert.Conversion: + return d.FromDB(*s) + } } var sv reflect.Value @@ -175,10 +491,10 @@ func convertAssign(dest, src interface{}) error { return nil } - return convertAssignV(reflect.ValueOf(dest), src) + return convertAssignV(reflect.ValueOf(dest), src, originalLocation, convertedLocation) } -func convertAssignV(dpv reflect.Value, src interface{}) error { +func convertAssignV(dpv reflect.Value, src interface{}, originalLocation, convertedLocation *time.Location) error { if dpv.Kind() != reflect.Ptr { return errors.New("destination not a pointer") } @@ -212,31 +528,28 @@ func convertAssignV(dpv reflect.Value, src interface{}) error { } dv.Set(reflect.New(dv.Type().Elem())) - return convertAssign(dv.Interface(), src) + return convertAssign(dv.Interface(), src, originalLocation, convertedLocation) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s := asString(src) - i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) + i64, err := asInt64(src) if err != nil { err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) } dv.SetInt(i64) return nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - s := asString(src) - u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) + u64, err := asUint64(src) if err != nil { err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) } dv.SetUint(u64) return nil case reflect.Float32, reflect.Float64: - s := asString(src) - f64, err := strconv.ParseFloat(s, dv.Type().Bits()) + f64, err := asFloat64(src) if err != nil { err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) } dv.SetFloat(f64) return nil @@ -376,3 +689,80 @@ func str2PK(s string, tp reflect.Type) (interface{}, error) { } return v.Interface(), nil } + +var ( + _ sql.Scanner = &NullUint64{} +) + +// NullUint64 represents an uint64 that may be null. +// NullUint64 implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullUint64 struct { + Uint64 uint64 + Valid bool +} + +// Scan implements the Scanner interface. +func (n *NullUint64) Scan(value interface{}) error { + if value == nil { + n.Uint64, n.Valid = 0, false + return nil + } + n.Valid = true + var err error + n.Uint64, err = asUint64(value) + return err +} + +// Value implements the driver Valuer interface. +func (n NullUint64) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Uint64, nil +} + +var ( + _ sql.Scanner = &NullUint32{} +) + +// NullUint32 represents an uint32 that may be null. +// NullUint32 implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullUint32 struct { + Uint32 uint32 + Valid bool // Valid is true if Uint32 is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullUint32) Scan(value interface{}) error { + if value == nil { + n.Uint32, n.Valid = 0, false + return nil + } + n.Valid = true + i64, err := asUint64(value) + if err != nil { + return err + } + n.Uint32 = uint32(i64) + return nil +} + +// Value implements the driver Valuer interface. +func (n NullUint32) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return int64(n.Uint32), nil +} + +var ( + _ sql.Scanner = &EmptyScanner{} +) + +type EmptyScanner struct{} + +func (EmptyScanner) Scan(value interface{}) error { + return nil +} diff --git a/dialects/driver.go b/dialects/driver.go index c511b665..0b6187d3 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -18,9 +18,14 @@ type ScanContext struct { UserLocation *time.Location } +type DriverFeatures struct { + SupportNullable bool +} + // Driver represents a database driver type Driver interface { Parse(string, string) (*URI, error) + Features() DriverFeatures GenScanResult(string) (interface{}, error) // according given column type generating a suitable scan interface Scan(*ScanContext, *core.Rows, []*sql.ColumnType, ...interface{}) error } @@ -77,3 +82,9 @@ type baseDriver struct{} func (b *baseDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, v ...interface{}) error { return rows.Scan(v...) } + +func (b *baseDriver) Features() DriverFeatures { + return DriverFeatures{ + SupportNullable: true, + } +} diff --git a/dialects/mysql.go b/dialects/mysql.go index 03bc9a4b..a341ce05 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -633,6 +633,7 @@ func (db *mysql) Filters() []Filter { } type mysqlDriver struct { + baseDriver } func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { diff --git a/dialects/postgres.go b/dialects/postgres.go index e4641509..a2611c60 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1302,6 +1302,12 @@ type pqDriver struct { baseDriver } +func (b *pqDriver) Features() DriverFeatures { + return DriverFeatures{ + SupportNullable: false, + } +} + type values map[string]string func (vs values) Set(k, v string) { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 306f377c..1bc0b218 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -576,3 +576,9 @@ func (p *sqlite3Driver) GenScanResult(colType string) (interface{}, error) { return &r, nil } } + +func (b *sqlite3Driver) Features() DriverFeatures { + return DriverFeatures{ + SupportNullable: false, + } +} diff --git a/scan.go b/scan.go index e11d6e8d..c5cb77ff 100644 --- a/scan.go +++ b/scan.go @@ -6,12 +6,120 @@ package xorm import ( "database/sql" + "fmt" + "reflect" + "time" "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/dialects" ) +// genScanResultsByBeanNullabale generates scan result +func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { + switch t := bean.(type) { + case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes: + return t, false, nil + case *time.Time: + return &sql.NullTime{}, true, nil + case *string: + return &sql.NullString{}, true, nil + case *int, *int8, *int16, *int32: + return &sql.NullInt32{}, true, nil + case *int64: + return &sql.NullInt64{}, true, nil + case *uint, *uint8, *uint16, *uint32: + return &NullUint32{}, true, nil + case *uint64: + return &NullUint64{}, true, nil + case *float32, *float64: + return &sql.NullFloat64{}, true, nil + case *bool: + return &sql.NullBool{}, true, nil + case sql.NullInt64, sql.NullBool, sql.NullFloat64, sql.NullString, + time.Time, + string, + int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64, + bool: + return nil, false, fmt.Errorf("unsupported scan type: %t", t) + case convert.Conversion: + return &sql.RawBytes{}, true, nil + } + + tp := reflect.TypeOf(bean).Elem() + switch tp.Kind() { + case reflect.String: + return &sql.NullString{}, true, nil + case reflect.Int64: + return &sql.NullInt64{}, true, nil + case reflect.Int32, reflect.Int, reflect.Int16, reflect.Int8: + return &sql.NullInt32{}, true, nil + case reflect.Uint64: + return &NullUint64{}, true, nil + case reflect.Uint32, reflect.Uint, reflect.Uint16, reflect.Uint8: + return &NullUint32{}, true, nil + default: + return nil, false, fmt.Errorf("unsupported type: %#v", bean) + } +} + +func genScanResultsByBean(bean interface{}) (interface{}, bool, error) { + switch t := bean.(type) { + case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, + *string, + *int, *int8, *int16, *int32, *int64, + *uint, *uint8, *uint16, *uint32, *uint64, + *float32, *float64, + *bool: + return t, false, nil + case *time.Time: + return &sql.NullTime{}, true, nil + case sql.NullInt64, sql.NullBool, sql.NullFloat64, sql.NullString, + time.Time, + string, + int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + bool: + return nil, false, fmt.Errorf("unsupported scan type: %t", t) + case convert.Conversion: + return &sql.RawBytes{}, true, nil + } + + tp := reflect.TypeOf(bean).Elem() + switch tp.Kind() { + case reflect.String: + return new(string), true, nil + case reflect.Int64: + return new(int64), true, nil + case reflect.Int32: + return new(int32), true, nil + case reflect.Int: + return new(int32), true, nil + case reflect.Int16: + return new(int32), true, nil + case reflect.Int8: + return new(int32), true, nil + case reflect.Uint64: + return new(uint64), true, nil + case reflect.Uint32: + return new(uint32), true, nil + case reflect.Uint: + return new(uint), true, nil + case reflect.Uint16: + return new(uint16), true, nil + case reflect.Uint8: + return new(uint8), true, nil + case reflect.Float32: + return new(float32), true, nil + case reflect.Float64: + return new(float64), true, nil + default: + return nil, false, fmt.Errorf("unsupported type: %#v", bean) + } +} + func row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { var scanResults = make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { @@ -50,18 +158,97 @@ func row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (ma return result, nil } -func row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { - results := make([]string, 0, len(fields)) - var scanResults = make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { +func (engine *Engine) scanStringInterface(rows *core.Rows, types []*sql.ColumnType) ([]interface{}, error) { + var scanResults = make([]interface{}, len(types)) + for i := 0; i < len(types); i++ { var s sql.NullString scanResults[i] = &s } - if err := rows.Scan(scanResults...); err != nil { + if err := engine.driver.Scan(&dialects.ScanContext{ + DBLocation: engine.DatabaseTZ, + UserLocation: engine.TZLocation, + }, rows, types, scanResults...); err != nil { + return nil, err + } + return scanResults, nil +} + +// scan is a wrap of driver.Scan but will automatically change the input values according requirements +func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.ColumnType, vv ...interface{}) error { + var scanResults = make([]interface{}, 0, len(types)) + var replaces = make([]bool, 0, len(types)) + var err error + for _, v := range vv { + var replaced bool + var scanResult interface{} + if _, ok := v.(sql.Scanner); !ok { + var useNullable = true + if engine.driver.Features().SupportNullable { + nullable, ok := types[0].Nullable() + useNullable = ok && nullable + } + + if useNullable { + scanResult, replaced, err = genScanResultsByBeanNullable(v) + } else { + scanResult, replaced, err = genScanResultsByBean(v) + } + if err != nil { + return err + } + } else { + scanResult = v + } + scanResults = append(scanResults, scanResult) + replaces = append(replaces, replaced) + } + + var scanCtx = dialects.ScanContext{ + DBLocation: engine.DatabaseTZ, + UserLocation: engine.TZLocation, + } + + if err = engine.driver.Scan(&scanCtx, rows, types, scanResults...); err != nil { + return err + } + + for i, replaced := range replaces { + if replaced { + if err = convertAssign(vv[i], scanResults[i], scanCtx.DBLocation, engine.TZLocation); err != nil { + return err + } + } + } + + return nil +} + +func (engine *Engine) scanInterfaces(rows *core.Rows, types []*sql.ColumnType) ([]interface{}, error) { + var scanResultContainers = make([]interface{}, len(types)) + for i := 0; i < len(types); i++ { + scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName()) + if err != nil { + return nil, err + } + scanResultContainers[i] = scanResult + } + if err := engine.driver.Scan(&dialects.ScanContext{ + DBLocation: engine.DatabaseTZ, + UserLocation: engine.TZLocation, + }, rows, types, scanResultContainers...); err != nil { + return nil, err + } + return scanResultContainers, nil +} + +func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { + scanResults, err := engine.scanStringInterface(rows, types) + if err != nil { return nil, err } + var results = make([]string, 0, len(fields)) for i := 0; i < len(fields); i++ { results = append(results, scanResults[i].(*sql.NullString).String) } diff --git a/session_find.go b/session_find.go index 0daea005..261e6b7f 100644 --- a/session_find.go +++ b/session_find.go @@ -276,7 +276,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { cols := table.PKColumns() if len(cols) == 1 { - return convertAssign(dst, pk[0]) + return convertAssign(dst, pk[0], nil, nil) } dst = pk diff --git a/session_get.go b/session_get.go index e303176d..cb2bda75 100644 --- a/session_get.go +++ b/session_get.go @@ -6,12 +6,16 @@ package xorm import ( "database/sql" + "database/sql/driver" "errors" "fmt" "reflect" "strconv" + "time" "xorm.io/xorm/caches" + "xorm.io/xorm/convert" + "xorm.io/xorm/core" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -108,6 +112,17 @@ func (session *Session) get(bean interface{}) (bool, error) { return true, nil } +var ( + valuerTypePlaceHolder driver.Valuer + valuerType = reflect.TypeOf(&valuerTypePlaceHolder).Elem() + + scannerTypePlaceHolder sql.Scanner + scannerType = reflect.TypeOf(&scannerTypePlaceHolder).Elem() + + conversionTypePlaceHolder convert.Conversion + conversionType = reflect.TypeOf(&conversionTypePlaceHolder).Elem() +) + func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { @@ -122,155 +137,161 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, return false, nil } - switch bean.(type) { - case sql.NullInt64, sql.NullBool, sql.NullFloat64, sql.NullString: - return true, rows.Scan(&bean) - case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString: - return true, rows.Scan(bean) - case *string: - var res sql.NullString - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*string)) = res.String - } - return true, nil - case *int: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*int)) = int(res.Int64) - } - return true, nil - case *int8: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*int8)) = int8(res.Int64) - } - return true, nil - case *int16: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*int16)) = int16(res.Int64) - } - return true, nil - case *int32: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*int32)) = int32(res.Int64) - } - return true, nil - case *int64: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*int64)) = int64(res.Int64) - } - return true, nil - case *uint: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*uint)) = uint(res.Int64) - } - return true, nil - case *uint8: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*uint8)) = uint8(res.Int64) - } - return true, nil - case *uint16: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*uint16)) = uint16(res.Int64) - } - return true, nil - case *uint32: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*uint32)) = uint32(res.Int64) - } - return true, nil - case *uint64: - var res sql.NullInt64 - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*uint64)) = uint64(res.Int64) - } - return true, nil - case *bool: - var res sql.NullBool - if err := rows.Scan(&res); err != nil { - return true, err - } - if res.Valid { - *(bean.(*bool)) = res.Bool - } - return true, nil + // WARN: Alougth rows return true, but we may also return error. + types, err := rows.ColumnTypes() + if err != nil { + return true, err + } + fields, err := rows.Columns() + if err != nil { + return true, err } - switch beanKind { case reflect.Struct: - fields, err := rows.Columns() - if err != nil { - // WARN: Alougth rows return true, but get fields failed - return true, err + if _, ok := bean.(*time.Time); ok { + break } - - scanResults, err := session.row2Slice(rows, fields, bean) - if err != nil { - return false, err + if _, ok := bean.(sql.Scanner); ok { + break } - // close it before convert data - rows.Close() - - dataStruct := utils.ReflectValue(bean) - _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table) - if err != nil { - return true, err + if _, ok := bean.(convert.Conversion); len(types) == 1 && ok { + break } - - return true, session.executeProcessors() + return session.getStruct(rows, types, fields, table, bean) case reflect.Slice: - err = rows.ScanSlice(bean) + return session.getSlice(rows, types, fields, bean) case reflect.Map: - err = rows.ScanMap(bean) - case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - err = rows.Scan(bean) - default: - err = rows.Scan(bean) + return session.getMap(rows, types, fields, bean) } - return true, err + return session.getVars(rows, types, fields, bean) +} + +func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) (bool, error) { + switch t := bean.(type) { + case *[]string: + res, err := session.engine.scanStringInterface(rows, types) + if err != nil { + return true, err + } + + var needAppend = len(*t) == 0 // both support slice is empty or has been initlized + for i, r := range res { + if needAppend { + *t = append(*t, r.(*sql.NullString).String) + } else { + (*t)[i] = r.(*sql.NullString).String + } + } + return true, nil + case *[]interface{}: + scanResults, err := session.engine.scanInterfaces(rows, types) + if err != nil { + return true, err + } + var needAppend = len(*t) == 0 + for ii := range fields { + s, err := convert.Interface2Interface(session.engine.DatabaseTZ, scanResults[ii]) + if err != nil { + return true, err + } + if needAppend { + *t = append(*t, s) + } else { + (*t)[ii] = s + } + } + return true, nil + default: + return true, fmt.Errorf("unspoorted slice type: %t", t) + } +} + +func (session *Session) getMap(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) (bool, error) { + switch t := bean.(type) { + case *map[string]string: + scanResults, err := session.engine.scanStringInterface(rows, types) + if err != nil { + return true, err + } + for ii, key := range fields { + (*t)[key] = scanResults[ii].(*sql.NullString).String + } + return true, nil + case *map[string]interface{}: + scanResults, err := session.engine.scanInterfaces(rows, types) + if err != nil { + return true, err + } + for ii, key := range fields { + s, err := convert.Interface2Interface(session.engine.DatabaseTZ, scanResults[ii]) + if err != nil { + return true, err + } + (*t)[key] = s + } + return true, nil + default: + return true, fmt.Errorf("unspoorted map type: %t", t) + } +} + +func (session *Session) getVars(rows *core.Rows, types []*sql.ColumnType, fields []string, beans ...interface{}) (bool, error) { + if len(beans) != len(types) { + return false, fmt.Errorf("expected columns %d, but only %d variables", len(types), len(beans)) + } + var scanResults = make([]interface{}, 0, len(types)) + var replaceds = make([]bool, 0, len(types)) + for _, bean := range beans { + switch t := bean.(type) { + case sql.Scanner: + scanResults = append(scanResults, t) + replaceds = append(replaceds, false) + case convert.Conversion: + scanResults = append(scanResults, &sql.RawBytes{}) + replaceds = append(replaceds, true) + default: + scanResults = append(scanResults, bean) + replaceds = append(replaceds, false) + } + } + + err := session.engine.scan(rows, fields, types, scanResults...) + if err != nil { + return true, err + } + for i, replaced := range replaceds { + if replaced { + err = convertAssign(beans[i], scanResults[i], session.engine.DatabaseTZ, session.engine.TZLocation) + if err != nil { + return true, err + } + } + } + return true, nil +} + +func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) (bool, error) { + fields, err := rows.Columns() + if err != nil { + // WARN: Alougth rows return true, but get fields failed + return true, err + } + + scanResults, err := session.row2Slice(rows, fields, bean) + if err != nil { + return false, err + } + // close it before convert data + rows.Close() + + dataStruct := utils.ReflectValue(bean) + _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table) + if err != nil { + return true, err + } + + return true, session.executeProcessors() } func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) { diff --git a/session_insert.go b/session_insert.go index e733e06e..7f8f3008 100644 --- a/session_insert.go +++ b/session_insert.go @@ -375,7 +375,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - return 1, convertAssignV(aiValue.Addr(), id) + return 1, convertAssignV(aiValue.Addr(), id, session.engine.DatabaseTZ, session.engine.TZLocation) } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.URI().DBType == schemas.POSTGRES || session.engine.dialect.URI().DBType == schemas.MSSQL) { res, err := session.queryBytes(sqlStr, args...) @@ -415,7 +415,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - return 1, convertAssignV(aiValue.Addr(), id) + return 1, convertAssignV(aiValue.Addr(), id, session.engine.DatabaseTZ, session.engine.TZLocation) } res, err := session.exec(sqlStr, args...) @@ -455,7 +455,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return res.RowsAffected() } - if err := convertAssignV(aiValue.Addr(), id); err != nil { + if err := convertAssignV(aiValue.Addr(), id, session.engine.DatabaseTZ, session.engine.TZLocation); err != nil { return 0, err } diff --git a/session_query.go b/session_query.go index 01cd6f44..fa33496d 100644 --- a/session_query.go +++ b/session_query.go @@ -54,7 +54,7 @@ func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]stri } for rows.Next() { - record, err := row2sliceStr(rows, types, fields) + record, err := session.engine.row2sliceStr(rows, types, fields) if err != nil { return nil, err } From 8f64a78cd4b287c4f24e789767b15f229b0b7ee7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 6 Jul 2021 17:11:45 +0800 Subject: [PATCH 070/179] Support delete with no bean (#1926) Now you can use delete like this: ``` orm.Table("my_table").Where("id=?",1).Delete() ``` Reviewed-on: https://gitea.com/xorm/xorm/pulls/1926 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- README.md | 19 +++++--- README_CN.md | 3 ++ engine.go | 4 +- integrations/session_delete_test.go | 25 ++++++++++ interface.go | 2 +- session_delete.go | 76 +++++++++++++++++------------ 6 files changed, 87 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 67380839..40826f13 100644 --- a/README.md +++ b/README.md @@ -245,35 +245,38 @@ for rows.Next() { ```Go affected, err := engine.ID(1).Update(&user) -// UPDATE user SET ... Where id = ? +// UPDATE user SET ... WHERE id = ? affected, err := engine.Update(&user, &User{Name:name}) -// UPDATE user SET ... Where name = ? +// UPDATE user SET ... WHERE name = ? var ids = []int64{1, 2, 3} affected, err := engine.In("id", ids).Update(&user) -// UPDATE user SET ... Where id IN (?, ?, ?) +// UPDATE user SET ... WHERE id IN (?, ?, ?) // force update indicated columns by Cols affected, err := engine.ID(1).Cols("age").Update(&User{Name:name, Age: 12}) -// UPDATE user SET age = ?, updated=? Where id = ? +// UPDATE user SET age = ?, updated=? WHERE id = ? // force NOT update indicated columns by Omit affected, err := engine.ID(1).Omit("name").Update(&User{Name:name, Age: 12}) -// UPDATE user SET age = ?, updated=? Where id = ? +// UPDATE user SET age = ?, updated=? WHERE id = ? affected, err := engine.ID(1).AllCols().Update(&user) -// UPDATE user SET name=?,age=?,salt=?,passwd=?,updated=? Where id = ? +// UPDATE user SET name=?,age=?,salt=?,passwd=?,updated=? WHERE id = ? ``` * `Delete` delete one or more records, Delete MUST have condition ```Go affected, err := engine.Where(...).Delete(&user) -// DELETE FROM user Where ... +// DELETE FROM user WHERE ... affected, err := engine.ID(2).Delete(&user) -// DELETE FROM user Where id = ? +// DELETE FROM user WHERE id = ? + +affected, err := engine.Table("user").Where(...).Delete() +// DELETE FROM user WHERE ... ``` * `Count` count records diff --git a/README_CN.md b/README_CN.md index 80245dd3..06706417 100644 --- a/README_CN.md +++ b/README_CN.md @@ -271,6 +271,9 @@ affected, err := engine.Where(...).Delete(&user) affected, err := engine.ID(2).Delete(&user) // DELETE FROM user Where id = ? + +affected, err := engine.Table("user").Where(...).Delete() +// DELETE FROM user WHERE ... ``` * `Count` 获取记录条数 diff --git a/engine.go b/engine.go index 1064e8e1..a45771a2 100644 --- a/engine.go +++ b/engine.go @@ -1202,10 +1202,10 @@ func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64 } // Delete records, bean's non-empty fields are conditions -func (engine *Engine) Delete(bean interface{}) (int64, error) { +func (engine *Engine) Delete(beans ...interface{}) (int64, error) { session := engine.NewSession() defer session.Close() - return session.Delete(bean) + return session.Delete(beans...) } // Get retrieve one record from table, bean's non-empty fields diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index cc7e861d..56f6f5b8 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -241,3 +241,28 @@ func TestUnscopeDelete(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func TestDelete2(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type UserinfoDelete2 struct { + Uid int64 `xorm:"id pk not null autoincr"` + IsMan bool + } + + assert.NoError(t, testEngine.Sync2(new(UserinfoDelete2))) + + user := UserinfoDelete2{} + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + cnt, err = testEngine.Table("userinfo_delete2").In("id", []int{1}).Delete() + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + user2 := UserinfoDelete2{} + has, err := testEngine.ID(1).Get(&user2) + assert.NoError(t, err) + assert.False(t, has) +} diff --git a/interface.go b/interface.go index d31323ff..fbb81015 100644 --- a/interface.go +++ b/interface.go @@ -30,7 +30,7 @@ type Interface interface { CreateUniques(bean interface{}) error Decr(column string, arg ...interface{}) *Session Desc(...string) *Session - Delete(interface{}) (int64, error) + Delete(...interface{}) (int64, error) Distinct(columns ...string) *Session DropIndexes(bean interface{}) error Exec(sqlOrArgs ...interface{}) (sql.Result, error) diff --git a/session_delete.go b/session_delete.go index 13bf791f..baabb558 100644 --- a/session_delete.go +++ b/session_delete.go @@ -83,7 +83,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri } // Delete records, bean's non-empty fields are conditions -func (session *Session) Delete(bean interface{}) (int64, error) { +func (session *Session) Delete(beans ...interface{}) (int64, error) { if session.isAutoClose { defer session.Close() } @@ -92,20 +92,32 @@ func (session *Session) Delete(bean interface{}) (int64, error) { return 0, session.statement.LastError } - if err := session.statement.SetRefBean(bean); err != nil { - return 0, err + var ( + condSQL string + condArgs []interface{} + err error + bean interface{} + ) + if len(beans) > 0 { + bean = beans[0] + if err = session.statement.SetRefBean(bean); err != nil { + return 0, err + } + + executeBeforeClosures(session, bean) + + if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok { + processor.BeforeDelete() + } + + condSQL, condArgs, err = session.statement.GenConds(bean) + } else { + condSQL, condArgs, err = session.statement.GenCondSQL(session.statement.Conds()) } - - executeBeforeClosures(session, bean) - - if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok { - processor.BeforeDelete() - } - - condSQL, condArgs, err := session.statement.GenConds(bean) if err != nil { return 0, err } + pLimitN := session.statement.LimitN if len(condSQL) == 0 && (pLimitN == nil || *pLimitN == 0) { return 0, ErrNeedDeletedCond @@ -156,7 +168,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { var realSQL string argsForCache := make([]interface{}, 0, len(condArgs)*2) - if session.statement.GetUnscoped() || table.DeletedColumn() == nil { // tag "deleted" is disabled + if session.statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled realSQL = deleteSQL copy(argsForCache, condArgs) argsForCache = append(condArgs, argsForCache...) @@ -220,27 +232,29 @@ func (session *Session) Delete(bean interface{}) (int64, error) { return 0, err } - // handle after delete processors - if session.isAutoCommit { - for _, closure := range session.afterClosures { - closure(bean) - } - if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { - processor.AfterDelete() - } - } else { - lenAfterClosures := len(session.afterClosures) - if lenAfterClosures > 0 { - if value, has := session.afterDeleteBeans[bean]; has && value != nil { - *value = append(*value, session.afterClosures...) - } else { - afterClosures := make([]func(interface{}), lenAfterClosures) - copy(afterClosures, session.afterClosures) - session.afterDeleteBeans[bean] = &afterClosures + if bean != nil { + // handle after delete processors + if session.isAutoCommit { + for _, closure := range session.afterClosures { + closure(bean) + } + if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { + processor.AfterDelete() } } else { - if _, ok := interface{}(bean).(AfterDeleteProcessor); ok { - session.afterDeleteBeans[bean] = nil + lenAfterClosures := len(session.afterClosures) + if lenAfterClosures > 0 && len(beans) > 0 { + if value, has := session.afterDeleteBeans[beans[0]]; has && value != nil { + *value = append(*value, session.afterClosures...) + } else { + afterClosures := make([]func(interface{}), lenAfterClosures) + copy(afterClosures, session.afterClosures) + session.afterDeleteBeans[bean] = &afterClosures + } + } else { + if _, ok := interface{}(bean).(AfterDeleteProcessor); ok { + session.afterDeleteBeans[bean] = nil + } } } } From c433fd51cb154e42e4cd0163e17545616f25bdbd Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 6 Jul 2021 23:20:17 +0800 Subject: [PATCH 071/179] Nil ptr is nullable (#1919) replace #661 Co-authored-by: Jim Salem Co-authored-by: Oleh Herych Reviewed-on: https://gitea.com/xorm/xorm/pulls/1919 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/types_test.go | 45 ++++++++++++++++++++++++++++++++------ interface.go | 1 + 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/integrations/types_test.go b/integrations/types_test.go index 539171d5..f192c1ff 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -147,13 +147,39 @@ func (s *SliceType) ToDB() ([]byte, error) { return json.DefaultJSONHandler.Marshal(s) } +type Nullable struct { + Data string +} + +func (s *Nullable) FromDB(data []byte) error { + if data == nil { + return nil + } + + *s = Nullable{ + Data: string(data), + } + + return nil +} + +func (s *Nullable) ToDB() ([]byte, error) { + if s == nil { + return nil, nil + } + + return []byte(s.Data), nil +} + type ConvStruct struct { - Conv ConvString - Conv2 *ConvString - Cfg1 ConvConfig - Cfg2 *ConvConfig `xorm:"TEXT"` - Cfg3 convert.Conversion `xorm:"BLOB"` - Slice SliceType + Conv ConvString + Conv2 *ConvString + Cfg1 ConvConfig + Cfg2 *ConvConfig `xorm:"TEXT"` + Cfg3 convert.Conversion `xorm:"BLOB"` + Slice SliceType + Nullable1 *Nullable `xorm:"null"` + Nullable2 *Nullable `xorm:"null"` } func (c *ConvStruct) BeforeSet(name string, cell xorm.Cell) { @@ -176,8 +202,10 @@ func TestConversion(t *testing.T) { c.Cfg2 = &ConvConfig{"xx", 2} c.Cfg3 = &ConvConfig{"zz", 3} c.Slice = []*ConvConfig{{"yy", 4}, {"ff", 5}} + c.Nullable1 = &Nullable{Data: "test"} + c.Nullable2 = nil - _, err := testEngine.Insert(c) + _, err := testEngine.Nullable("nullable2").Insert(c) assert.NoError(t, err) c1 := new(ConvStruct) @@ -219,6 +247,9 @@ func TestConversion(t *testing.T) { assert.EqualValues(t, 2, len(c2.Slice)) assert.EqualValues(t, *c.Slice[0], *c2.Slice[0]) assert.EqualValues(t, *c.Slice[1], *c2.Slice[1]) + assert.NotNil(t, c1.Nullable1) + assert.Equal(t, c1.Nullable1.Data, "test") + assert.Nil(t, c1.Nullable2) } type MyInt int diff --git a/interface.go b/interface.go index fbb81015..5d68f536 100644 --- a/interface.go +++ b/interface.go @@ -51,6 +51,7 @@ type Interface interface { MustCols(columns ...string) *Session NoAutoCondition(...bool) *Session NotIn(string, ...interface{}) *Session + Nullable(...string) *Session Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session Omit(columns ...string) *Session OrderBy(order string) *Session From bb91a0773cbcac4755fbb568c3832c43a32069d5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 11:34:33 +0800 Subject: [PATCH 072/179] Fix postgres genScanResult (#1972) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1972 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/postgres.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index a2611c60..fd6d871c 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1387,26 +1387,22 @@ func (p *pqDriver) GenScanResult(colType string) (interface{}, error) { case "VARCHAR", "TEXT": var s sql.NullString return &s, nil - case "BIGINT": + case "BIGINT", "BIGSERIAL": var s sql.NullInt64 return &s, nil - case "TINYINT", "INT", "INT8", "INT4": + case "SMALLINT", "INT", "INT8", "INT4", "INTEGER", "SERIAL": var s sql.NullInt32 return &s, nil - case "FLOAT", "FLOAT4": + case "FLOAT", "FLOAT4", "REAL", "DOUBLE PRECISION": var s sql.NullFloat64 return &s, nil case "DATETIME", "TIMESTAMP": var s sql.NullTime return &s, nil - case "BIT": - var s sql.RawBytes - return &s, nil case "BOOL": var s sql.NullBool return &s, nil default: - fmt.Printf("unknow postgres database type: %v\n", colType) var r sql.RawBytes return &r, nil } From bece9a6373852d5e4faf51758a4dec969ce01cd9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 13:03:05 +0800 Subject: [PATCH 073/179] refactor slice2Bean (#1974) as title. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1974 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 2 + session.go | 800 ++++++++++++++++++++++++++--------------------------- 2 files changed, 402 insertions(+), 400 deletions(-) diff --git a/convert.go b/convert.go index f7d733ad..67183098 100644 --- a/convert.go +++ b/convert.go @@ -238,6 +238,8 @@ func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { func convertAssign(dest, src interface{}, originalLocation *time.Location, convertedLocation *time.Location) error { // Common cases, without reflect. switch s := src.(type) { + case *interface{}: + return convertAssign(dest, *s, originalLocation, convertedLocation) case string: switch d := dest.(type) { case *string: diff --git a/session.go b/session.go index 6df9e20d..3fb92991 100644 --- a/session.go +++ b/session.go @@ -436,6 +436,397 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa return scanResults, nil } +func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflect.Value, + scanResult interface{}, table *schemas.Table) error { + rawValue := reflect.Indirect(reflect.ValueOf(scanResult)) + + // if row is null then ignore + if rawValue.Interface() == nil { + return nil + } + + if fieldValue.CanAddr() { + if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { + data, err := value2Bytes(&rawValue) + if err != nil { + return err + } + if err := structConvert.FromDB(data); err != nil { + return err + } + return nil + } + } + + if _, ok := fieldValue.Interface().(convert.Conversion); ok { + if data, err := value2Bytes(&rawValue); err == nil { + if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + } + fieldValue.Interface().(convert.Conversion).FromDB(data) + } else { + return err + } + return nil + } + + rawValueType := reflect.TypeOf(rawValue.Interface()) + vv := reflect.ValueOf(rawValue.Interface()) + + fieldType := fieldValue.Type() + + if col.IsJSON { + var bs []byte + if rawValueType.Kind() == reflect.String { + bs = []byte(vv.String()) + } else if rawValueType.ConvertibleTo(schemas.BytesType) { + bs = vv.Bytes() + } else { + return fmt.Errorf("unsupported database data type: %s %v", col.Name, rawValueType.Kind()) + } + + if len(bs) > 0 { + if fieldType.Kind() == reflect.String { + fieldValue.SetString(string(bs)) + return nil + } + if fieldValue.CanAddr() { + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + return err + } + } else { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + } + return nil + } + + switch fieldType.Kind() { + case reflect.Complex64, reflect.Complex128: + // TODO: reimplement this + var bs []byte + if rawValueType.Kind() == reflect.String { + bs = []byte(vv.String()) + } else if rawValueType.ConvertibleTo(schemas.BytesType) { + bs = vv.Bytes() + } + + if len(bs) > 0 { + if fieldValue.CanAddr() { + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + return err + } + } else { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + } + return nil + case reflect.Slice, reflect.Array: + switch rawValueType.Kind() { + case reflect.Slice, reflect.Array: + switch rawValueType.Elem().Kind() { + case reflect.Uint8: + if fieldType.Elem().Kind() == reflect.Uint8 { + if col.SQLType.IsText() { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } else { + if fieldValue.Len() > 0 { + for i := 0; i < fieldValue.Len(); i++ { + if i < vv.Len() { + fieldValue.Index(i).Set(vv.Index(i)) + } + } + } else { + for i := 0; i < vv.Len(); i++ { + fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) + } + } + } + return nil + } + } + } + case reflect.String: + if rawValueType.Kind() == reflect.String { + fieldValue.SetString(vv.String()) + return nil + } + case reflect.Bool: + if rawValueType.Kind() == reflect.Bool { + fieldValue.SetBool(vv.Bool()) + return nil + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch rawValueType.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetInt(vv.Int()) + return nil + } + case reflect.Float32, reflect.Float64: + switch rawValueType.Kind() { + case reflect.Float32, reflect.Float64: + fieldValue.SetFloat(vv.Float()) + return nil + } + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + switch rawValueType.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + fieldValue.SetUint(vv.Uint()) + return nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetUint(uint64(vv.Int())) + return nil + } + case reflect.Struct: + if fieldType.ConvertibleTo(schemas.TimeType) { + dbTZ := session.engine.DatabaseTZ + if col.TimeZone != nil { + dbTZ = col.TimeZone + } + + if rawValueType == schemas.TimeType { + t := vv.Convert(schemas.TimeType).Interface().(time.Time) + + z, _ := t.Zone() + // set new location if database don't save timezone or give an incorrect timezone + if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location + session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", col.Name, t, z, *t.Location()) + t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t.Minute(), t.Second(), t.Nanosecond(), dbTZ) + } + + t = t.In(session.engine.TZLocation) + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + return nil + } else if rawValueType == schemas.IntType || rawValueType == schemas.Int64Type || + rawValueType == schemas.Int32Type { + t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation) + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + return nil + } else { + if d, ok := vv.Interface().([]uint8); ok { + t, err := session.byte2Time(col, d) + if err != nil { + session.engine.logger.Errorf("byte2Time error: %v", err) + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + return nil + } + + } else if d, ok := vv.Interface().(string); ok { + t, err := session.str2Time(col, d) + if err != nil { + session.engine.logger.Errorf("byte2Time error: %v", err) + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + return nil + } + } else { + return fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface()) + } + } + } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { + err := nulVal.Scan(vv.Interface()) + if err == nil { + return nil + } + session.engine.logger.Errorf("sql.Sanner error: %v", err) + } else if col.IsJSON { + if rawValueType.Kind() == reflect.String { + x := reflect.New(fieldType) + if len([]byte(vv.String())) > 0 { + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + return nil + } else if rawValueType.Kind() == reflect.Slice { + x := reflect.New(fieldType) + if len(vv.Bytes()) > 0 { + err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + return nil + } + } else if session.statement.UseCascade { + table, err := session.engine.tagParser.ParseWithCache(*fieldValue) + if err != nil { + return err + } + + if len(table.PrimaryKeys) != 1 { + return errors.New("unsupported non or composited primary key cascade") + } + var pk = make(schemas.PK, len(table.PrimaryKeys)) + pk[0], err = asKind(vv, rawValueType) + if err != nil { + return err + } + + if !pk.IsZero() { + // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch + // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne + // property to be fetched lazily + structInter := reflect.New(fieldValue.Type()) + has, err := session.ID(pk).NoCascade().get(structInter.Interface()) + if err != nil { + return err + } + if has { + fieldValue.Set(structInter.Elem()) + } else { + return errors.New("cascade obj is not exist") + } + } + return nil + } + case reflect.Ptr: + // !nashtsai! TODO merge duplicated codes above + switch fieldType { + // following types case matching ptr's native type, therefore assign ptr directly + case schemas.PtrStringType: + if rawValueType.Kind() == reflect.String { + x := vv.String() + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrBoolType: + if rawValueType.Kind() == reflect.Bool { + x := vv.Bool() + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrTimeType: + if rawValueType == schemas.PtrTimeType { + var x = rawValue.Interface().(time.Time) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrFloat64Type: + if rawValueType.Kind() == reflect.Float64 { + x := vv.Float() + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrUint64Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint64(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrInt64Type: + if rawValueType.Kind() == reflect.Int64 { + x := vv.Int() + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrFloat32Type: + if rawValueType.Kind() == reflect.Float64 { + var x = float32(vv.Float()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrIntType: + if rawValueType.Kind() == reflect.Int64 { + var x = int(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrInt32Type: + if rawValueType.Kind() == reflect.Int64 { + var x = int32(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrInt8Type: + if rawValueType.Kind() == reflect.Int64 { + var x = int8(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrInt16Type: + if rawValueType.Kind() == reflect.Int64 { + var x = int16(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrUintType: + if rawValueType.Kind() == reflect.Int64 { + var x = uint(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.PtrUint32Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint32(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.Uint8Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint8(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.Uint16Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint16(vv.Int()) + fieldValue.Set(reflect.ValueOf(&x)) + return nil + } + case schemas.Complex64Type: + var x complex64 + if len([]byte(vv.String())) > 0 { + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) + if err != nil { + return err + } + fieldValue.Set(reflect.ValueOf(&x)) + } + return nil + case schemas.Complex128Type: + var x complex128 + if len([]byte(vv.String())) > 0 { + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) + if err != nil { + return err + } + fieldValue.Set(reflect.ValueOf(&x)) + } + return nil + } // switch fieldType + } // switch fieldType.Kind() + + data, err := value2Bytes(&rawValue) + if err != nil { + return err + } + + return session.bytes2Value(col, fieldValue, data) +} + func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { defer func() { executeAfterSet(bean, fields, scanResults) @@ -447,14 +838,19 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b var pk schemas.PK for ii, key := range fields { var idx int - var ok bool var lKey = strings.ToLower(key) + var ok bool + if idx, ok = tempMap[lKey]; !ok { idx = 0 } else { idx = idx + 1 } + tempMap[lKey] = idx + col := table.GetColumnIdx(key, idx) + + var scanResult = scanResults[ii] fieldValue, err := session.getField(dataStruct, key, table, idx) if err != nil { @@ -466,408 +862,12 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b if fieldValue == nil { continue } - rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) - // if row is null then ignore - if rawValue.Interface() == nil { - continue + if err := session.convertBeanField(col, fieldValue, scanResult, table); err != nil { + return nil, err } - - if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - if err := structConvert.FromDB(data); err != nil { - return nil, err - } - } else { - return nil, err - } - continue - } - } - - if _, ok := fieldValue.Interface().(convert.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) - } - fieldValue.Interface().(convert.Conversion).FromDB(data) - } else { - return nil, err - } - continue - } - - rawValueType := reflect.TypeOf(rawValue.Interface()) - vv := reflect.ValueOf(rawValue.Interface()) - col := table.GetColumnIdx(key, idx) if col.IsPrimaryKey { - pk = append(pk, rawValue.Interface()) - } - fieldType := fieldValue.Type() - hasAssigned := false - - if col.IsJSON { - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(schemas.BytesType) { - bs = vv.Bytes() - } else { - return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) - } - - hasAssigned = true - - if len(bs) > 0 { - if fieldType.Kind() == reflect.String { - fieldValue.SetString(string(bs)) - continue - } - if fieldValue.CanAddr() { - err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - return nil, err - } - } else { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) - } - } - - continue - } - - switch fieldType.Kind() { - case reflect.Complex64, reflect.Complex128: - // TODO: reimplement this - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(schemas.BytesType) { - bs = vv.Bytes() - } - - hasAssigned = true - if len(bs) > 0 { - if fieldValue.CanAddr() { - err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - return nil, err - } - } else { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) - } - } - case reflect.Slice, reflect.Array: - switch rawValueType.Kind() { - case reflect.Slice, reflect.Array: - switch rawValueType.Elem().Kind() { - case reflect.Uint8: - if fieldType.Elem().Kind() == reflect.Uint8 { - hasAssigned = true - if col.SQLType.IsText() { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) - } else { - if fieldValue.Len() > 0 { - for i := 0; i < fieldValue.Len(); i++ { - if i < vv.Len() { - fieldValue.Index(i).Set(vv.Index(i)) - } - } - } else { - for i := 0; i < vv.Len(); i++ { - fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) - } - } - } - } - } - } - case reflect.String: - if rawValueType.Kind() == reflect.String { - hasAssigned = true - fieldValue.SetString(vv.String()) - } - case reflect.Bool: - if rawValueType.Kind() == reflect.Bool { - hasAssigned = true - fieldValue.SetBool(vv.Bool()) - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch rawValueType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - hasAssigned = true - fieldValue.SetInt(vv.Int()) - } - case reflect.Float32, reflect.Float64: - switch rawValueType.Kind() { - case reflect.Float32, reflect.Float64: - hasAssigned = true - fieldValue.SetFloat(vv.Float()) - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - switch rawValueType.Kind() { - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - hasAssigned = true - fieldValue.SetUint(vv.Uint()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - hasAssigned = true - fieldValue.SetUint(uint64(vv.Int())) - } - case reflect.Struct: - if fieldType.ConvertibleTo(schemas.TimeType) { - dbTZ := session.engine.DatabaseTZ - if col.TimeZone != nil { - dbTZ = col.TimeZone - } - - if rawValueType == schemas.TimeType { - hasAssigned = true - - t := vv.Convert(schemas.TimeType).Interface().(time.Time) - - z, _ := t.Zone() - // set new location if database don't save timezone or give an incorrect timezone - if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location - session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) - t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), - t.Minute(), t.Second(), t.Nanosecond(), dbTZ) - } - - t = t.In(session.engine.TZLocation) - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } else if rawValueType == schemas.IntType || rawValueType == schemas.Int64Type || - rawValueType == schemas.Int32Type { - hasAssigned = true - - t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation) - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } else { - if d, ok := vv.Interface().([]uint8); ok { - hasAssigned = true - t, err := session.byte2Time(col, d) - if err != nil { - session.engine.logger.Errorf("byte2Time error: %v", err) - hasAssigned = false - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } - } else if d, ok := vv.Interface().(string); ok { - hasAssigned = true - t, err := session.str2Time(col, d) - if err != nil { - session.engine.logger.Errorf("byte2Time error: %v", err) - hasAssigned = false - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } - } else { - return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface()) - } - } - } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { - // !! 增加支持sql.Scanner接口的结构,如sql.NullString - hasAssigned = true - if err := nulVal.Scan(vv.Interface()); err != nil { - session.engine.logger.Errorf("sql.Sanner error: %v", err) - hasAssigned = false - } - } else if col.IsJSON { - if rawValueType.Kind() == reflect.String { - hasAssigned = true - x := reflect.New(fieldType) - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) - } - } else if rawValueType.Kind() == reflect.Slice { - hasAssigned = true - x := reflect.New(fieldType) - if len(vv.Bytes()) > 0 { - err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) - } - } - } else if session.statement.UseCascade { - table, err := session.engine.tagParser.ParseWithCache(*fieldValue) - if err != nil { - return nil, err - } - - hasAssigned = true - if len(table.PrimaryKeys) != 1 { - return nil, errors.New("unsupported non or composited primary key cascade") - } - var pk = make(schemas.PK, len(table.PrimaryKeys)) - pk[0], err = asKind(vv, rawValueType) - if err != nil { - return nil, err - } - - if !pk.IsZero() { - // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch - // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne - // property to be fetched lazily - structInter := reflect.New(fieldValue.Type()) - has, err := session.ID(pk).NoCascade().get(structInter.Interface()) - if err != nil { - return nil, err - } - if has { - fieldValue.Set(structInter.Elem()) - } else { - return nil, errors.New("cascade obj is not exist") - } - } - } - case reflect.Ptr: - // !nashtsai! TODO merge duplicated codes above - switch fieldType { - // following types case matching ptr's native type, therefore assign ptr directly - case schemas.PtrStringType: - if rawValueType.Kind() == reflect.String { - x := vv.String() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrBoolType: - if rawValueType.Kind() == reflect.Bool { - x := vv.Bool() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrTimeType: - if rawValueType == schemas.PtrTimeType { - hasAssigned = true - var x = rawValue.Interface().(time.Time) - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrFloat64Type: - if rawValueType.Kind() == reflect.Float64 { - x := vv.Float() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrUint64Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint64(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt64Type: - if rawValueType.Kind() == reflect.Int64 { - x := vv.Int() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrFloat32Type: - if rawValueType.Kind() == reflect.Float64 { - var x = float32(vv.Float()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrIntType: - if rawValueType.Kind() == reflect.Int64 { - var x = int(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt32Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int32(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt8Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int8(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt16Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int16(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrUintType: - if rawValueType.Kind() == reflect.Int64 { - var x = uint(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrUint32Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint32(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.Uint8Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint8(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.Uint16Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint16(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.Complex64Type: - var x complex64 - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) - if err != nil { - return nil, err - } - fieldValue.Set(reflect.ValueOf(&x)) - } - hasAssigned = true - case schemas.Complex128Type: - var x complex128 - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) - if err != nil { - return nil, err - } - fieldValue.Set(reflect.ValueOf(&x)) - } - hasAssigned = true - } // switch fieldType - } // switch fieldType.Kind() - - // !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value - if !hasAssigned { - data, err := value2Bytes(&rawValue) - if err != nil { - return nil, err - } - - if err = session.bytes2Value(col, fieldValue, data); err != nil { - return nil, err - } + pk = append(pk, scanResult) } } return pk, nil From 54bbead2be07a68b5b8caceedb041c14f602cb7a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 13:59:48 +0800 Subject: [PATCH 074/179] refactor slice2Bean 2 (#1975) as title. Reviewed-on: https://gitea.com/xorm/xorm/pulls/1975 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- session.go | 132 +++++++---------------------------------------------- 1 file changed, 16 insertions(+), 116 deletions(-) diff --git a/session.go b/session.go index 3fb92991..a3b11889 100644 --- a/session.go +++ b/session.go @@ -472,7 +472,6 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec rawValueType := reflect.TypeOf(rawValue.Interface()) vv := reflect.ValueOf(rawValue.Interface()) - fieldType := fieldValue.Type() if col.IsJSON { @@ -508,6 +507,22 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } switch fieldType.Kind() { + case reflect.Ptr: + if scanResult == nil { + return nil + } + if v, ok := scanResult.(*interface{}); ok && v == nil { + return nil + } + + var e reflect.Value + if fieldValue.IsNil() { + e = reflect.New(fieldType.Elem()).Elem() + } else { + e = fieldValue.Elem() + } + + return session.convertBeanField(col, &e, scanResult, table) case reflect.Complex64, reflect.Complex128: // TODO: reimplement this var bs []byte @@ -702,121 +717,6 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } return nil } - case reflect.Ptr: - // !nashtsai! TODO merge duplicated codes above - switch fieldType { - // following types case matching ptr's native type, therefore assign ptr directly - case schemas.PtrStringType: - if rawValueType.Kind() == reflect.String { - x := vv.String() - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrBoolType: - if rawValueType.Kind() == reflect.Bool { - x := vv.Bool() - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrTimeType: - if rawValueType == schemas.PtrTimeType { - var x = rawValue.Interface().(time.Time) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrFloat64Type: - if rawValueType.Kind() == reflect.Float64 { - x := vv.Float() - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrUint64Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint64(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrInt64Type: - if rawValueType.Kind() == reflect.Int64 { - x := vv.Int() - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrFloat32Type: - if rawValueType.Kind() == reflect.Float64 { - var x = float32(vv.Float()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrIntType: - if rawValueType.Kind() == reflect.Int64 { - var x = int(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrInt32Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int32(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrInt8Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int8(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrInt16Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int16(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrUintType: - if rawValueType.Kind() == reflect.Int64 { - var x = uint(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.PtrUint32Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint32(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.Uint8Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint8(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.Uint16Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint16(vv.Int()) - fieldValue.Set(reflect.ValueOf(&x)) - return nil - } - case schemas.Complex64Type: - var x complex64 - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) - if err != nil { - return err - } - fieldValue.Set(reflect.ValueOf(&x)) - } - return nil - case schemas.Complex128Type: - var x complex128 - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) - if err != nil { - return err - } - fieldValue.Set(reflect.ValueOf(&x)) - } - return nil - } // switch fieldType } // switch fieldType.Kind() data, err := value2Bytes(&rawValue) From b754e78269bcd507b117e39d5d7b4064797fa2fc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 14:00:16 +0800 Subject: [PATCH 075/179] Support big.Float (#1973) Now you can use big.Float for numeric type. ```go type MyMoney struct { Id int64 Money big.Float `xorm:"numeric(22,2)"` } ``` Reviewed-on: https://gitea.com/xorm/xorm/pulls/1973 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 12 ++++++++ dialects/sqlite3.go | 2 +- integrations/session_get_test.go | 51 ++++++++++++++++++++++++++++++ internal/statements/values.go | 5 +++ scan.go | 24 +++++++++------ session_get.go | 53 +++++++++++--------------------- 6 files changed, 102 insertions(+), 45 deletions(-) diff --git a/convert.go b/convert.go index 67183098..491626a8 100644 --- a/convert.go +++ b/convert.go @@ -9,6 +9,7 @@ import ( "database/sql/driver" "errors" "fmt" + "math/big" "reflect" "strconv" "time" @@ -310,10 +311,12 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve if s.Valid { *d, _ = strconv.Atoi(s.String) } + return nil case *int64: if s.Valid { *d, _ = strconv.ParseInt(s.String, 10, 64) } + return nil case *string: if s.Valid { *d = s.String @@ -339,6 +342,15 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve d.Valid = true d.Time = *dt } + return nil + case *big.Float: + if s.Valid { + if d == nil { + d = big.NewFloat(0) + } + d.SetString(s.String) + } + return nil } case *sql.NullInt32: switch d := dest.(type) { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 1bc0b218..04e5b457 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -565,7 +565,7 @@ func (p *sqlite3Driver) GenScanResult(colType string) (interface{}, error) { case "REAL": var s sql.NullFloat64 return &s, nil - case "NUMERIC": + case "NUMERIC", "DECIMAL": var s sql.NullString return &s, nil case "BLOB": diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 99db98fc..6fc202bc 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -8,6 +8,7 @@ import ( "database/sql" "errors" "fmt" + "math/big" "strconv" "testing" "time" @@ -766,3 +767,53 @@ func TestGetNil(t *testing.T) { assert.True(t, errors.Is(err, xorm.ErrObjectIsNil)) assert.False(t, has) } + +func TestGetBigFloat(t *testing.T) { + type GetBigFloat struct { + Id int64 + Money *big.Float `xorm:"numeric(22,2)"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetBigFloat)) + + { + var gf = GetBigFloat{ + Money: big.NewFloat(999999.99), + } + _, err := testEngine.Insert(&gf) + assert.NoError(t, err) + + var m big.Float + has, err := testEngine.Table("get_big_float").Cols("money").Where("id=?", gf.Id).Get(&m) + assert.NoError(t, err) + assert.True(t, has) + assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) + //fmt.Println(m.Cmp(gf.Money)) + //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + } + + type GetBigFloat2 struct { + Id int64 + Money *big.Float `xorm:"decimal(22,2)"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetBigFloat2)) + + { + var gf2 = GetBigFloat2{ + Money: big.NewFloat(9999999.99), + } + _, err := testEngine.Insert(&gf2) + assert.NoError(t, err) + + var m2 big.Float + has, err := testEngine.Table("get_big_float2").Cols("money").Where("id=?", gf2.Id).Get(&m2) + assert.NoError(t, err) + assert.True(t, has) + assert.True(t, m2.String() == gf2.Money.String(), "%v != %v", m2.String(), gf2.Money.String()) + //fmt.Println(m.Cmp(gf.Money)) + //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + } +} diff --git a/internal/statements/values.go b/internal/statements/values.go index 71327c55..994070ac 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -8,6 +8,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "math/big" "reflect" "time" @@ -19,6 +20,7 @@ import ( var ( nullFloatType = reflect.TypeOf(sql.NullFloat64{}) + bigFloatType = reflect.TypeOf(big.Float{}) ) // Value2Interface convert a field value of a struct to interface for puting into database @@ -84,6 +86,9 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl return nil, nil } return t.Float64, nil + } else if fieldType.ConvertibleTo(bigFloatType) { + t := fieldValue.Convert(bigFloatType).Interface().(big.Float) + return t.String(), nil } if !col.IsJSON { diff --git a/scan.go b/scan.go index c5cb77ff..6396b097 100644 --- a/scan.go +++ b/scan.go @@ -7,6 +7,7 @@ package xorm import ( "database/sql" "fmt" + "math/big" "reflect" "time" @@ -182,13 +183,21 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column for _, v := range vv { var replaced bool var scanResult interface{} - if _, ok := v.(sql.Scanner); !ok { + switch t := v.(type) { + case sql.Scanner: + scanResult = t + case convert.Conversion: + scanResult = &sql.RawBytes{} + replaced = true + case *big.Float: + scanResult = &sql.NullString{} + replaced = true + default: var useNullable = true if engine.driver.Features().SupportNullable { nullable, ok := types[0].Nullable() useNullable = ok && nullable } - if useNullable { scanResult, replaced, err = genScanResultsByBeanNullable(v) } else { @@ -197,25 +206,22 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column if err != nil { return err } - } else { - scanResult = v } + scanResults = append(scanResults, scanResult) replaces = append(replaces, replaced) } - var scanCtx = dialects.ScanContext{ + if err = engine.driver.Scan(&dialects.ScanContext{ DBLocation: engine.DatabaseTZ, UserLocation: engine.TZLocation, - } - - if err = engine.driver.Scan(&scanCtx, rows, types, scanResults...); err != nil { + }, rows, types, scanResults...); err != nil { return err } for i, replaced := range replaces { if replaced { - if err = convertAssign(vv[i], scanResults[i], scanCtx.DBLocation, engine.TZLocation); err != nil { + if err = convertAssign(vv[i], scanResults[i], engine.DatabaseTZ, engine.TZLocation); err != nil { return err } } diff --git a/session_get.go b/session_get.go index cb2bda75..58255033 100644 --- a/session_get.go +++ b/session_get.go @@ -9,6 +9,7 @@ import ( "database/sql/driver" "errors" "fmt" + "math/big" "reflect" "strconv" "time" @@ -123,6 +124,20 @@ var ( conversionType = reflect.TypeOf(&conversionTypePlaceHolder).Elem() ) +func isScannableStruct(bean interface{}, typeLen int) bool { + switch bean.(type) { + case *time.Time: + return false + case sql.Scanner: + return false + case convert.Conversion: + return typeLen > 1 + case *big.Float: + return false + } + return true +} + func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { @@ -148,13 +163,7 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, } switch beanKind { case reflect.Struct: - if _, ok := bean.(*time.Time); ok { - break - } - if _, ok := bean.(sql.Scanner); ok { - break - } - if _, ok := bean.(convert.Conversion); len(types) == 1 && ok { + if !isScannableStruct(bean, len(types)) { break } return session.getStruct(rows, types, fields, table, bean) @@ -240,35 +249,9 @@ func (session *Session) getVars(rows *core.Rows, types []*sql.ColumnType, fields if len(beans) != len(types) { return false, fmt.Errorf("expected columns %d, but only %d variables", len(types), len(beans)) } - var scanResults = make([]interface{}, 0, len(types)) - var replaceds = make([]bool, 0, len(types)) - for _, bean := range beans { - switch t := bean.(type) { - case sql.Scanner: - scanResults = append(scanResults, t) - replaceds = append(replaceds, false) - case convert.Conversion: - scanResults = append(scanResults, &sql.RawBytes{}) - replaceds = append(replaceds, true) - default: - scanResults = append(scanResults, bean) - replaceds = append(replaceds, false) - } - } - err := session.engine.scan(rows, fields, types, scanResults...) - if err != nil { - return true, err - } - for i, replaced := range replaceds { - if replaced { - err = convertAssign(beans[i], scanResults[i], session.engine.DatabaseTZ, session.engine.TZLocation) - if err != nil { - return true, err - } - } - } - return true, nil + err := session.engine.scan(rows, fields, types, beans...) + return true, err } func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) (bool, error) { From 46fd8f58b3b925ac7305b879e0c1f4a2fc8ad140 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 15:46:21 +0800 Subject: [PATCH 076/179] Get struct and Find support big.Float (#1976) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1976 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 89 ++++++++++++++++++++++++++++---- integrations/session_get_test.go | 12 +++++ schemas/type.go | 5 +- session.go | 34 ++++++++---- 4 files changed, 120 insertions(+), 20 deletions(-) diff --git a/convert.go b/convert.go index 491626a8..20a6e373 100644 --- a/convert.go +++ b/convert.go @@ -104,9 +104,7 @@ func asInt64(src interface{}) (int64, error) { return rv.Int(), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return int64(rv.Uint()), nil - case reflect.Float64: - return int64(rv.Float()), nil - case reflect.Float32: + case reflect.Float64, reflect.Float32: return int64(rv.Float()), nil case reflect.String: return strconv.ParseInt(rv.String(), 10, 64) @@ -154,9 +152,7 @@ func asUint64(src interface{}) (uint64, error) { return uint64(rv.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return uint64(rv.Uint()), nil - case reflect.Float64: - return uint64(rv.Float()), nil - case reflect.Float32: + case reflect.Float64, reflect.Float32: return uint64(rv.Float()), nil case reflect.String: return strconv.ParseUint(rv.String(), 10, 64) @@ -204,9 +200,7 @@ func asFloat64(src interface{}) (float64, error) { return float64(rv.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return float64(rv.Uint()), nil - case reflect.Float64: - return float64(rv.Float()), nil - case reflect.Float32: + case reflect.Float64, reflect.Float32: return float64(rv.Float()), nil case reflect.String: return strconv.ParseFloat(rv.String(), 64) @@ -214,6 +208,83 @@ func asFloat64(src interface{}) (float64, error) { return 0, fmt.Errorf("unsupported value %T as int64", src) } +func asBigFloat(src interface{}) (*big.Float, error) { + res := big.NewFloat(0) + switch v := src.(type) { + case int: + res.SetInt64(int64(v)) + return res, nil + case int16: + res.SetInt64(int64(v)) + return res, nil + case int32: + res.SetInt64(int64(v)) + return res, nil + case int8: + res.SetInt64(int64(v)) + return res, nil + case int64: + res.SetInt64(int64(v)) + return res, nil + case uint: + res.SetUint64(uint64(v)) + return res, nil + case uint8: + res.SetUint64(uint64(v)) + return res, nil + case uint16: + res.SetUint64(uint64(v)) + return res, nil + case uint32: + res.SetUint64(uint64(v)) + return res, nil + case uint64: + res.SetUint64(uint64(v)) + return res, nil + case []byte: + res.SetString(string(v)) + return res, nil + case string: + res.SetString(v) + return res, nil + case *sql.NullString: + if v.Valid { + res.SetString(v.String) + return res, nil + } + return nil, nil + case *sql.NullInt32: + if v.Valid { + res.SetInt64(int64(v.Int32)) + return res, nil + } + return nil, nil + case *sql.NullInt64: + if v.Valid { + res.SetInt64(int64(v.Int64)) + return res, nil + } + return nil, nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + res.SetInt64(rv.Int()) + return res, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + res.SetUint64(rv.Uint()) + return res, nil + case reflect.Float64, reflect.Float32: + res.SetFloat64(rv.Float()) + return res, nil + case reflect.String: + res.SetString(rv.String()) + return res, nil + } + return nil, fmt.Errorf("unsupported value %T as big.Float", src) +} + func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 6fc202bc..02b060b1 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -815,5 +815,17 @@ func TestGetBigFloat(t *testing.T) { assert.True(t, m2.String() == gf2.Money.String(), "%v != %v", m2.String(), gf2.Money.String()) //fmt.Println(m.Cmp(gf.Money)) //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + + var gf3 GetBigFloat2 + has, err = testEngine.ID(gf2.Id).Get(&gf3) + assert.NoError(t, err) + assert.True(t, has) + assert.True(t, gf3.Money.String() == gf2.Money.String(), "%v != %v", gf3.Money.String(), gf2.Money.String()) + + var gfs []GetBigFloat2 + err = testEngine.Find(&gfs) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(gfs)) + assert.True(t, gfs[0].Money.String() == gf2.Money.String(), "%v != %v", gfs[0].Money.String(), gf2.Money.String()) } } diff --git a/schemas/type.go b/schemas/type.go index fc02f015..3846b5ee 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -5,6 +5,7 @@ package schemas import ( + "math/big" "reflect" "sort" "strings" @@ -240,6 +241,7 @@ var ( intDefault int uintDefault uint timeDefault time.Time + bigFloatDefault big.Float ) // enumerates all types @@ -267,7 +269,8 @@ var ( ByteType = reflect.TypeOf(byteDefault) BytesType = reflect.SliceOf(ByteType) - TimeType = reflect.TypeOf(timeDefault) + TimeType = reflect.TypeOf(timeDefault) + BigFloatType = reflect.TypeOf(bigFloatDefault) ) // enumerates all types diff --git a/session.go b/session.go index a3b11889..64b1758a 100644 --- a/session.go +++ b/session.go @@ -438,8 +438,15 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflect.Value, scanResult interface{}, table *schemas.Table) error { - rawValue := reflect.Indirect(reflect.ValueOf(scanResult)) + v, ok := scanResult.(*interface{}) + if ok { + scanResult = *v + } + if scanResult == nil { + return nil + } + rawValue := reflect.Indirect(reflect.ValueOf(scanResult)) // if row is null then ignore if rawValue.Interface() == nil { return nil @@ -508,21 +515,19 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec switch fieldType.Kind() { case reflect.Ptr: - if scanResult == nil { - return nil - } - if v, ok := scanResult.(*interface{}); ok && v == nil { - return nil - } - var e reflect.Value if fieldValue.IsNil() { e = reflect.New(fieldType.Elem()).Elem() } else { e = fieldValue.Elem() } - - return session.convertBeanField(col, &e, scanResult, table) + if err := session.convertBeanField(col, &e, scanResult, table); err != nil { + return err + } + if fieldValue.IsNil() { + fieldValue.Set(e.Addr()) + } + return nil case reflect.Complex64, reflect.Complex128: // TODO: reimplement this var bs []byte @@ -610,6 +615,15 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec return nil } case reflect.Struct: + if fieldType.ConvertibleTo(schemas.BigFloatType) { + v, err := asBigFloat(scanResult) + if err != nil { + return err + } + fieldValue.Set(reflect.ValueOf(v).Elem().Convert(fieldType)) + return nil + } + if fieldType.ConvertibleTo(schemas.TimeType) { dbTZ := session.engine.DatabaseTZ if col.TimeZone != nil { From a38b1fddb33050b8378fb7ba292185935ba89a76 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 17:00:58 +0800 Subject: [PATCH 077/179] Add tests for github.com/shopspring/decimal support (#1977) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1977 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- go.mod | 1 + go.sum | 2 ++ integrations/session_get_test.go | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/go.mod b/go.mod index f6e4af90..78d8d7d4 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.7.0 github.com/mattn/go-sqlite3 v1.14.6 + github.com/shopspring/decimal v1.2.0 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 diff --git a/go.sum b/go.sum index 3c79850c..85953202 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 02b060b1..f4338b4f 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -17,6 +17,7 @@ import ( "xorm.io/xorm/contexts" "xorm.io/xorm/schemas" + "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" ) @@ -829,3 +830,54 @@ func TestGetBigFloat(t *testing.T) { assert.True(t, gfs[0].Money.String() == gf2.Money.String(), "%v != %v", gfs[0].Money.String(), gf2.Money.String()) } } + +func TestGetDecimal(t *testing.T) { + type GetDecimal struct { + Id int64 + Money decimal.Decimal `xorm:"decimal(22,2)"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetDecimal)) + + { + var gf = GetDecimal{ + Money: decimal.NewFromFloat(999999.99), + } + _, err := testEngine.Insert(&gf) + assert.NoError(t, err) + + var m decimal.Decimal + has, err := testEngine.Table("get_decimal").Cols("money").Where("id=?", gf.Id).Get(&m) + assert.NoError(t, err) + assert.True(t, has) + assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) + //fmt.Println(m.Cmp(gf.Money)) + //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + } + + type GetDecimal2 struct { + Id int64 + Money *decimal.Decimal `xorm:"decimal(22,2)"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetDecimal2)) + + { + v := decimal.NewFromFloat(999999.99) + var gf = GetDecimal2{ + Money: &v, + } + _, err := testEngine.Insert(&gf) + assert.NoError(t, err) + + var m decimal.Decimal + has, err := testEngine.Table("get_decimal2").Cols("money").Where("id=?", gf.Id).Get(&m) + assert.NoError(t, err) + assert.True(t, has) + assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) + //fmt.Println(m.Cmp(gf.Money)) + //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + } +} From 717e4a0d2177e14d02a3cd74aa4bf5e9f88a3731 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 17:09:40 +0800 Subject: [PATCH 078/179] Add database alias table and fix wrong warning (#1947) fix #1831 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1947 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dialect.go | 6 ++++++ dialects/mysql.go | 15 +++++++++++++++ dialects/postgres.go | 12 ++++++++++++ integrations/session_schema_test.go | 13 +++++++++++++ schemas/type.go | 6 ++++++ session_schema.go | 6 ++++-- 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 325836b4..b3d374cc 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -43,6 +43,7 @@ type Dialect interface { Init(*URI) error URI() *URI SQLType(*schemas.Column) string + Alias(string) string // return what a sql type's alias of FormatBytes(b []byte) string Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) @@ -80,6 +81,11 @@ type Base struct { quoter schemas.Quoter } +// Alias returned col itself +func (db *Base) Alias(col string) string { + return col +} + // Quoter returns the current database Quoter func (db *Base) Quoter() schemas.Quoter { return db.quoter diff --git a/dialects/mysql.go b/dialects/mysql.go index a341ce05..da19b820 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -190,6 +190,21 @@ func (db *mysql) Init(uri *URI) error { return db.Base.Init(db, uri) } +var ( + mysqlColAliases = map[string]string{ + "numeric": "decimal", + } +) + +// Alias returns a alias of column +func (db *mysql) Alias(col string) string { + v, ok := mysqlColAliases[strings.ToLower(col)] + if ok { + return v + } + return col +} + func (db *mysql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { rows, err := queryer.QueryContext(ctx, "SELECT @@VERSION") if err != nil { diff --git a/dialects/postgres.go b/dialects/postgres.go index fd6d871c..9f3c7275 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -778,12 +778,24 @@ var ( var ( // DefaultPostgresSchema default postgres schema DefaultPostgresSchema = "public" + postgresColAliases = map[string]string{ + "numeric": "decimal", + } ) type postgres struct { Base } +// Alias returns a alias of column +func (db *postgres) Alias(col string) string { + v, ok := postgresColAliases[strings.ToLower(col)] + if ok { + return v + } + return col +} + func (db *postgres) Init(uri *URI) error { db.quoter = postgresQuoter return db.Base.Init(db, uri) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 28c75119..9cbebcbf 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -286,6 +286,19 @@ func TestSyncTable3(t *testing.T) { } } +func TestSyncTable4(t *testing.T) { + type SyncTable6 struct { + Id int64 + Qty float64 `xorm:"numeric(36,2)"` + } + + assert.NoError(t, PrepareEngine()) + + assert.NoError(t, testEngine.Sync2(new(SyncTable6))) + + assert.NoError(t, testEngine.Sync2(new(SyncTable6))) +} + func TestIsTableExist(t *testing.T) { assert.NoError(t, PrepareEngine()) diff --git a/schemas/type.go b/schemas/type.go index 3846b5ee..f49348be 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -368,3 +368,9 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf("") } } + +// SQLTypeName returns sql type name +func SQLTypeName(tp string) string { + fields := strings.Split(tp, "(") + return fields[0] +} diff --git a/session_schema.go b/session_schema.go index 7d36ae7f..7cfcb626 100644 --- a/session_schema.go +++ b/session_schema.go @@ -336,8 +336,10 @@ func (session *Session) Sync2(beans ...interface{}) error { } } else { if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') { - engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s", - tbNameWithSchema, col.Name, curType, expectedType) + if !strings.EqualFold(schemas.SQLTypeName(curType), engine.dialect.Alias(schemas.SQLTypeName(expectedType))) { + engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s", + tbNameWithSchema, col.Name, curType, expectedType) + } } } } else if expectedType == schemas.Varchar { From 375857b4bee1a1ea9ce1ca5e672edf13de497640 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 18:17:47 +0800 Subject: [PATCH 079/179] Add benchmark tests (#1978) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1978 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/performance_test.go | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 integrations/performance_test.go diff --git a/integrations/performance_test.go b/integrations/performance_test.go new file mode 100644 index 00000000..4b54b40c --- /dev/null +++ b/integrations/performance_test.go @@ -0,0 +1,104 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func BenchmarkGetVars(b *testing.B) { + b.StopTimer() + + assert.NoError(b, PrepareEngine()) + testEngine.ShowSQL(false) + + type BenchmarkGetVars struct { + Id int64 + Name string + } + + assert.NoError(b, testEngine.Sync2(new(BenchmarkGetVars))) + + var v = BenchmarkGetVars{ + Name: "myname", + } + _, err := testEngine.Insert(&v) + assert.NoError(b, err) + + b.StartTimer() + var myname string + for i := 0; i < b.N; i++ { + has, err := testEngine.Cols("name").Table("benchmark_get_vars").Where("id=?", v.Id).Get(&myname) + b.StopTimer() + myname = "" + assert.True(b, has) + assert.NoError(b, err) + b.StartTimer() + } +} + +func BenchmarkGetStruct(b *testing.B) { + b.StopTimer() + + assert.NoError(b, PrepareEngine()) + testEngine.ShowSQL(false) + + type BenchmarkGetStruct struct { + Id int64 + Name string + } + + assert.NoError(b, testEngine.Sync2(new(BenchmarkGetStruct))) + + var v = BenchmarkGetStruct{ + Name: "myname", + } + _, err := testEngine.Insert(&v) + assert.NoError(b, err) + + b.StartTimer() + var myname BenchmarkGetStruct + for i := 0; i < b.N; i++ { + has, err := testEngine.ID(v.Id).Get(&myname) + b.StopTimer() + myname.Id = 0 + myname.Name = "" + assert.True(b, has) + assert.NoError(b, err) + b.StartTimer() + } +} + +func BenchmarkFindStruct(b *testing.B) { + b.StopTimer() + + assert.NoError(b, PrepareEngine()) + testEngine.ShowSQL(false) + + type BenchmarkFindStruct struct { + Id int64 + Name string + } + + assert.NoError(b, testEngine.Sync2(new(BenchmarkFindStruct))) + + var v = BenchmarkFindStruct{ + Name: "myname", + } + _, err := testEngine.Insert(&v) + assert.NoError(b, err) + + b.StartTimer() + var mynames = make([]BenchmarkFindStruct, 0, 1) + for i := 0; i < b.N; i++ { + err := testEngine.Find(&mynames) + b.StopTimer() + mynames = make([]BenchmarkFindStruct, 0, 1) + assert.NoError(b, err) + b.StartTimer() + } +} From 27b1736c57c6243cd93e79bcce8c2bf060ba2890 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Jul 2021 19:16:36 +0800 Subject: [PATCH 080/179] Add test for get map with NULL column (#1948) Add tests for #1824 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1948 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_get_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index f4338b4f..9f82ce73 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -347,6 +347,29 @@ func TestGetSlice(t *testing.T) { assert.Error(t, err) } +func TestGetMap(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type UserinfoMap struct { + Uid int `xorm:"pk autoincr"` + IsMan bool + } + + assertSync(t, new(UserinfoMap)) + + tableName := testEngine.Quote(testEngine.TableName("userinfo_map", true)) + _, err := testEngine.Exec(fmt.Sprintf("INSERT INTO %s (is_man) VALUES (NULL)", tableName)) + assert.NoError(t, err) + + var valuesString = make(map[string]string) + has, err := testEngine.Table("userinfo_map").Get(&valuesString) + assert.NoError(t, err) + assert.Equal(t, true, has) + assert.Equal(t, 2, len(valuesString)) + assert.Equal(t, "1", valuesString["uid"]) + assert.Equal(t, "", valuesString["is_man"]) +} + func TestGetError(t *testing.T) { assert.NoError(t, PrepareEngine()) From dbd45f3f8e0f2d32f3932a2a23530ccaeb611d4e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 10 Jul 2021 23:27:55 +0800 Subject: [PATCH 081/179] set test timeout 20m (#1985) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1985 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- Makefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index bf71b0f4..1bdd44c9 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,7 @@ test: go-check test-cockroach: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=postgres -schema='$(TEST_COCKROACH_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ -conn_str="postgres://$(TEST_COCKROACH_USERNAME):$(TEST_COCKROACH_PASSWORD)@$(TEST_COCKROACH_HOST)/$(TEST_COCKROACH_DBNAME)?sslmode=disable&experimental_serial_normalization=sql_sequence" \ - -ignore_update_limit=true -coverprofile=cockroach.$(TEST_COCKROACH_SCHEMA).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -ignore_update_limit=true -coverprofile=cockroach.$(TEST_COCKROACH_SCHEMA).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-cockroach\#% test-cockroach\#%: go-check @@ -152,7 +152,7 @@ test-mssql: go-check -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ -default_varchar=$(TEST_MSSQL_DEFAULT_VARCHAR) -default_char=$(TEST_MSSQL_DEFAULT_CHAR) \ -do_nvarchar_override_test=$(TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST) \ - -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PNONY: test-mssql\#% test-mssql\#%: go-check @@ -166,7 +166,7 @@ test-mssql\#%: go-check test-mymysql: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mymysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="tcp:$(TEST_MYSQL_HOST)*$(TEST_MYSQL_DBNAME)/$(TEST_MYSQL_USERNAME)/$(TEST_MYSQL_PASSWORD)" \ - -coverprofile=mymysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mymysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PNONY: test-mymysql\#% test-mymysql\#%: go-check @@ -178,7 +178,7 @@ test-mymysql\#%: go-check test-mysql: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \ - -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-mysql\#% test-mysql\#%: go-check @@ -190,7 +190,7 @@ test-mysql\#%: go-check test-postgres: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ -conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-postgres\#% test-postgres\#%: go-check @@ -201,12 +201,12 @@ test-postgres\#%: go-check .PHONY: test-sqlite3 test-sqlite3: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-sqlite3-schema test-sqlite3-schema: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-sqlite3\#% test-sqlite3\#%: go-check @@ -216,12 +216,12 @@ test-sqlite3\#%: go-check .PHONY: test-sqlite test-sqlite: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-sqlite-schema test-sqlite-schema: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-sqlite\#% test-sqlite\#%: go-check @@ -233,7 +233,7 @@ test-sqlite\#%: go-check test-tidb: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ -conn_str="$(TEST_TIDB_USERNAME):$(TEST_TIDB_PASSWORD)@tcp($(TEST_TIDB_HOST))/$(TEST_TIDB_DBNAME)" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=tidb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=tidb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-tidb\#% test-tidb\#%: go-check From 6f46e684259937a5233af527fc2bc5000260190e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 11 Jul 2021 09:30:33 +0800 Subject: [PATCH 082/179] Support Get time.Time (#1933) Fix #1107 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1933 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_get_test.go | 21 +++++++++++++++++++++ scan.go | 8 +++++--- session_get.go | 15 ++++++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 9f82ce73..ca894d59 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -904,3 +904,24 @@ func TestGetDecimal(t *testing.T) { //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) } } +func TestGetTime(t *testing.T) { + type GetTimeStruct struct { + Id int64 + CreateTime time.Time + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetTimeStruct)) + + var gts = GetTimeStruct{ + CreateTime: time.Now(), + } + _, err := testEngine.Insert(>s) + assert.NoError(t, err) + + var gn time.Time + has, err := testEngine.Table("get_time_struct").Cols(colMapper.Obj2Table("CreateTime")).Get(&gn) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, gts.CreateTime.Format(time.RFC3339), gn.Format(time.RFC3339)) +} diff --git a/scan.go b/scan.go index 6396b097..d668208a 100644 --- a/scan.go +++ b/scan.go @@ -22,7 +22,9 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes: return t, false, nil case *time.Time: - return &sql.NullTime{}, true, nil + return &sql.NullString{}, true, nil + case *sql.NullTime: + return &sql.NullString{}, true, nil case *string: return &sql.NullString{}, true, nil case *int, *int8, *int16, *int32: @@ -75,8 +77,8 @@ func genScanResultsByBean(bean interface{}) (interface{}, bool, error) { *float32, *float64, *bool: return t, false, nil - case *time.Time: - return &sql.NullTime{}, true, nil + case *time.Time, *sql.NullTime: + return &sql.NullString{}, true, nil case sql.NullInt64, sql.NullBool, sql.NullFloat64, sql.NullString, time.Time, string, diff --git a/session_get.go b/session_get.go index 58255033..f710a0b1 100644 --- a/session_get.go +++ b/session_get.go @@ -35,6 +35,19 @@ func (session *Session) Get(bean interface{}) (bool, error) { return session.get(bean) } +func isPtrOfTime(v interface{}) bool { + if _, ok := v.(*time.Time); ok { + return true + } + + el := reflect.ValueOf(v).Elem() + if el.Kind() != reflect.Struct { + return false + } + + return el.Type().ConvertibleTo(schemas.TimeType) +} + func (session *Session) get(bean interface{}) (bool, error) { defer session.resetStatement() @@ -51,7 +64,7 @@ func (session *Session) get(bean interface{}) (bool, error) { return false, ErrObjectIsNil } - if beanValue.Elem().Kind() == reflect.Struct { + if beanValue.Elem().Kind() == reflect.Struct && !isPtrOfTime(bean) { if err := session.statement.SetRefBean(bean); err != nil { return false, err } From 8bf97de140c8af4ba4f61a9275a3ecb5b56d28f6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 11 Jul 2021 20:05:43 +0800 Subject: [PATCH 083/179] Fix bug on dumptable (#1984) Fix #1983 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1984 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 1 - convert/time.go | 9 +- convert/time_test.go | 30 +++++ engine.go | 212 +++++++++++------------------------- integrations/engine_test.go | 13 +++ schemas/type.go | 8 +- 6 files changed, 121 insertions(+), 152 deletions(-) create mode 100644 convert/time_test.go diff --git a/convert.go b/convert.go index 20a6e373..69277734 100644 --- a/convert.go +++ b/convert.go @@ -348,7 +348,6 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve *d = cloneBytes(s) return nil } - case time.Time: switch d := dest.(type) { case *string: diff --git a/convert/time.go b/convert/time.go index 8901279b..696b301c 100644 --- a/convert/time.go +++ b/convert/time.go @@ -19,7 +19,14 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t dt = dt.In(convertedLocation) return &dt, nil } else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' { - dt, err := time.ParseInLocation("2006-01-02T15:04:05Z", s, originalLocation) + dt, err := time.ParseInLocation(time.RFC3339, s, originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil + } else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' { + dt, err := time.Parse(time.RFC3339, s) if err != nil { return nil, err } diff --git a/convert/time_test.go b/convert/time_test.go new file mode 100644 index 00000000..ef01b362 --- /dev/null +++ b/convert/time_test.go @@ -0,0 +1,30 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestString2Time(t *testing.T) { + expectedLoc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + + var kases = map[string]time.Time{ + "2021-06-06T22:58:20+08:00": time.Date(2021, 6, 6, 22, 58, 20, 0, expectedLoc), + "2021-07-11 10:44:00": time.Date(2021, 7, 11, 18, 44, 0, 0, expectedLoc), + "2021-08-10T10:33:04Z": time.Date(2021, 8, 10, 18, 33, 04, 0, expectedLoc), + } + for layout, tm := range kases { + t.Run(layout, func(t *testing.T) { + target, err := String2Time(layout, time.UTC, expectedLoc) + assert.NoError(t, err) + assert.EqualValues(t, tm, *target) + }) + } +} diff --git a/engine.go b/engine.go index a45771a2..d3ee8a8c 100644 --- a/engine.go +++ b/engine.go @@ -13,7 +13,6 @@ import ( "os" "reflect" "runtime" - "strconv" "strings" "time" @@ -21,7 +20,6 @@ import ( "xorm.io/xorm/contexts" "xorm.io/xorm/core" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -446,93 +444,14 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return engine.dumpTables(tables, w, tp...) } -func formatColumnValue(dbLocation *time.Location, dstDialect dialects.Dialect, d interface{}, col *schemas.Column) string { - if d == nil { - return "NULL" - } - - if dq, ok := d.(bool); ok && (dstDialect.URI().DBType == schemas.SQLITE || - dstDialect.URI().DBType == schemas.MSSQL) { - if dq { +func formatBool(s string, dstDialect dialects.Dialect) string { + if dstDialect.URI().DBType == schemas.MSSQL { + switch s { + case "true": return "1" + case "false": + return "0" } - return "0" - } - - if col.SQLType.IsText() { - var v string - switch reflect.TypeOf(d).Kind() { - case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map: - bytes, err := json.DefaultJSONHandler.Marshal(d) - if err != nil { - v = fmt.Sprintf("%s", d) - } else { - v = string(bytes) - } - default: - v = fmt.Sprintf("%s", d) - } - - return "'" + strings.Replace(v, "'", "''", -1) + "'" - } else if col.SQLType.IsTime() { - if t, ok := d.(time.Time); ok { - return "'" + t.In(dbLocation).Format("2006-01-02 15:04:05") + "'" - } - var v = fmt.Sprintf("%s", d) - if strings.HasSuffix(v, " +0000 UTC") { - return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 UTC")]) - } else if strings.HasSuffix(v, " +0000 +0000") { - return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 +0000")]) - } - return "'" + strings.Replace(v, "'", "''", -1) + "'" - } else if col.SQLType.IsBlob() { - if reflect.TypeOf(d).Kind() == reflect.Slice { - return fmt.Sprintf("%s", dstDialect.FormatBytes(d.([]byte))) - } else if reflect.TypeOf(d).Kind() == reflect.String { - return fmt.Sprintf("'%s'", d.(string)) - } - } else if col.SQLType.IsNumeric() { - switch reflect.TypeOf(d).Kind() { - case reflect.Slice: - if col.SQLType.Name == schemas.Bool { - return fmt.Sprintf("%v", strconv.FormatBool(d.([]byte)[0] != byte('0'))) - } - return fmt.Sprintf("%s", string(d.([]byte))) - case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int: - if col.SQLType.Name == schemas.Bool { - v := reflect.ValueOf(d).Int() > 0 - if dstDialect.URI().DBType == schemas.SQLITE { - if v { - return "1" - } - return "0" - } - return fmt.Sprintf("%v", strconv.FormatBool(v)) - } - return fmt.Sprintf("%d", d) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if col.SQLType.Name == schemas.Bool { - v := reflect.ValueOf(d).Uint() > 0 - if dstDialect.URI().DBType == schemas.SQLITE { - if v { - return "1" - } - return "0" - } - return fmt.Sprintf("%v", strconv.FormatBool(v)) - } - return fmt.Sprintf("%d", d) - default: - return fmt.Sprintf("%v", d) - } - } - - s := fmt.Sprintf("%v", d) - if strings.Contains(s, ":") || strings.Contains(s, "-") { - if strings.HasSuffix(s, " +0000 UTC") { - return fmt.Sprintf("'%s'", s[0:len(s)-len(" +0000 UTC")]) - } - return fmt.Sprintf("'%s'", s) } return s } @@ -545,7 +464,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } else { dstDialect = dialects.QueryDialect(tp[0]) if dstDialect == nil { - return errors.New("Unsupported database type") + return fmt.Errorf("unsupported database type %v", tp[0]) } uri := engine.dialect.URI() @@ -619,73 +538,68 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } defer rows.Close() - if table.Type != nil { - sess := engine.NewSession() - defer sess.Close() - for rows.Next() { - beanValue := reflect.New(table.Type) - bean := beanValue.Interface() - fields, err := rows.Columns() - if err != nil { - return err - } - scanResults, err := sess.row2Slice(rows, fields, bean) - if err != nil { - return err - } + types, err := rows.ColumnTypes() + if err != nil { + return err + } - dataStruct := utils.ReflectValue(bean) - _, err = sess.slice2Bean(scanResults, fields, bean, &dataStruct, table) - if err != nil { - return err - } + sess := engine.NewSession() + defer sess.Close() + for rows.Next() { + _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") + if err != nil { + return err + } - _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") - if err != nil { - return err - } - - var temp string - for _, d := range dstCols { - col := table.GetColumn(d) - if col == nil { - return errors.New("unknown column error") + scanResults, err := sess.engine.scanStringInterface(rows, types) + if err != nil { + return err + } + for i, scanResult := range scanResults { + stp := schemas.SQLType{Name: types[i].DatabaseTypeName()} + if stp.IsNumeric() { + s := scanResult.(*sql.NullString) + if s.Valid { + if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "NULL"); err != nil { + return err + } + } + } else if stp.IsBool() { + s := scanResult.(*sql.NullString) + if s.Valid { + if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "NULL"); err != nil { + return err + } + } + } else { + s := scanResult.(*sql.NullString) + if s.Valid { + if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "NULL"); err != nil { + return err + } } - - field := dataStruct.FieldByIndex(col.FieldIndex) - temp += "," + formatColumnValue(engine.DatabaseTZ, dstDialect, field.Interface(), col) } - _, err = io.WriteString(w, temp[1:]+");\n") - if err != nil { - return err + if i < len(scanResults)-1 { + if _, err = io.WriteString(w, ","); err != nil { + return err + } } } - } else { - for rows.Next() { - dest := make([]interface{}, len(cols)) - err = rows.ScanSlice(&dest) - if err != nil { - return err - } - - _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") - if err != nil { - return err - } - - var temp string - for i, d := range dest { - col := table.GetColumn(cols[i]) - if col == nil { - return errors.New("unknow column error") - } - - temp += "," + formatColumnValue(engine.DatabaseTZ, dstDialect, d, col) - } - _, err = io.WriteString(w, temp[1:]+");\n") - if err != nil { - return err - } + _, err = io.WriteString(w, ");\n") + if err != nil { + return err } } diff --git a/integrations/engine_test.go b/integrations/engine_test.go index a06d91aa..a594ee46 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -172,8 +172,21 @@ func TestDumpTables(t *testing.T) { name := fmt.Sprintf("dump_%v-table.sql", tp) t.Run(name, func(t *testing.T) { assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, name, tp)) + }) } + + assert.NoError(t, testEngine.DropTables(new(TestDumpTableStruct))) + + importPath := fmt.Sprintf("dump_%v-table.sql", testEngine.Dialect().URI().DBType) + t.Run("import_"+importPath, func(t *testing.T) { + sess := testEngine.NewSession() + defer sess.Close() + assert.NoError(t, sess.Begin()) + _, err = sess.ImportFile(importPath) + assert.NoError(t, err) + assert.NoError(t, sess.Commit()) + }) } func TestDumpTables2(t *testing.T) { diff --git a/schemas/type.go b/schemas/type.go index f49348be..62e66c2e 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -39,6 +39,7 @@ const ( TIME_TYPE NUMERIC_TYPE ARRAY_TYPE + BOOL_TYPE ) // IsType reutrns ture if the column type is the same as the parameter @@ -64,6 +65,10 @@ func (s *SQLType) IsTime() bool { return s.IsType(TIME_TYPE) } +func (s *SQLType) IsBool() bool { + return s.IsType(BOOL_TYPE) +} + // IsNumeric returns true if column is a numeric type func (s *SQLType) IsNumeric() bool { return s.IsType(NUMERIC_TYPE) @@ -209,7 +214,8 @@ var ( Bytea: BLOB_TYPE, UniqueIdentifier: BLOB_TYPE, - Bool: NUMERIC_TYPE, + Bool: BOOL_TYPE, + Boolean: BOOL_TYPE, Serial: NUMERIC_TYPE, BigSerial: NUMERIC_TYPE, From 394c4e1f1715421c138d59561cae44f50423cb6c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 11 Jul 2021 21:33:01 +0800 Subject: [PATCH 084/179] Replace #1044 (#1935) Fix #1372, #765 TODO: - [x] Add tests Co-authored-by: MURAOKA Taro Reviewed-on: https://gitea.com/xorm/xorm/pulls/1935 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_update_test.go | 44 +++++++++++++++++++++++++++-- internal/statements/update.go | 10 ++++--- internal/statements/values.go | 15 ++++++++-- session.go | 2 +- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 796bfa0a..22808d60 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -1396,15 +1396,22 @@ func TestNilFromDB(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(TestTable1)) - cnt, err := testEngine.Insert(&TestTable1{ + var tt0 = TestTable1{ Field1: &TestFieldType1{ cb: []byte("string"), }, UpdateTime: time.Now(), - }) + } + cnt, err := testEngine.Insert(&tt0) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + var tt1 TestTable1 + has, err := testEngine.ID(tt0.Id).Get(&tt1) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "string", string(tt1.Field1.cb)) + cnt, err = testEngine.Update(TestTable1{ UpdateTime: time.Now().Add(time.Second), }, TestTable1{ @@ -1418,4 +1425,37 @@ func TestNilFromDB(t *testing.T) { }) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + + var tt = TestTable1{ + UpdateTime: time.Now(), + Field1: &TestFieldType1{ + cb: nil, + }, + } + cnt, err = testEngine.Insert(&tt) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var tt2 TestTable1 + has, err = testEngine.ID(tt.Id).Get(&tt2) + assert.NoError(t, err) + assert.True(t, has) + assert.Nil(t, tt2.Field1) + + var tt3 = TestTable1{ + UpdateTime: time.Now(), + Field1: &TestFieldType1{ + cb: []byte{}, + }, + } + cnt, err = testEngine.Insert(&tt3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var tt4 TestTable1 + has, err = testEngine.ID(tt3.Id).Get(&tt4) + assert.NoError(t, err) + assert.True(t, has) + assert.NotNil(t, tt4.Field1) + assert.NotNil(t, tt4.Field1.cb) } diff --git a/internal/statements/update.go b/internal/statements/update.go index 06cf0689..3020595b 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -127,8 +127,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, if err != nil { return nil, nil, err } - - val = data + if data != nil { + val = data + } goto APPEND } } @@ -138,8 +139,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, if err != nil { return nil, nil, err } - - val = data + if data != nil { + val = data + } goto APPEND } diff --git a/internal/statements/values.go b/internal/statements/values.go index 994070ac..ee3821e9 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -31,6 +31,12 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl if err != nil { return nil, err } + if data == nil { + if col.Nullable { + return nil, nil + } + data = []byte{} + } if col.SQLType.IsBlob() { return data, nil } @@ -45,12 +51,15 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl if err != nil { return nil, err } + if data == nil { + if col.Nullable { + return nil, nil + } + data = []byte{} + } if col.SQLType.IsBlob() { return data, nil } - if nil == data { - return nil, nil - } return string(data), nil } } diff --git a/session.go b/session.go index 64b1758a..486911a5 100644 --- a/session.go +++ b/session.go @@ -768,7 +768,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b fieldValue, err := session.getField(dataStruct, key, table, idx) if err != nil { - if !strings.Contains(err.Error(), "is not valid") { + if _, ok := err.(ErrFieldIsNotValid); !ok { session.engine.logger.Warnf("%v", err) } continue From 147328f6298a87593f1edf341ff07a23d785b673 Mon Sep 17 00:00:00 2001 From: andreasgerstmayr Date: Mon, 12 Jul 2021 23:51:50 +0800 Subject: [PATCH 085/179] fix possible null dereference in internal/statements/query.go (#1988) Make sure that pLimitN is not `nil` before dereferencing the pointer. Co-authored-by: Andreas Gerstmayr Reviewed-on: https://gitea.com/xorm/xorm/pulls/1988 Co-authored-by: andreasgerstmayr Co-committed-by: andreasgerstmayr --- internal/statements/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/statements/query.go b/internal/statements/query.go index a972a8e0..69f48e73 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -314,7 +314,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB fmt.Fprint(&buf, " LIMIT ", *pLimitN) } } else if dialect.URI().DBType == schemas.ORACLE { - if statement.Start != 0 || pLimitN != nil { + if statement.Start != 0 && pLimitN != nil { oldString := buf.String() buf.Reset() rawColStr := columnStr From b296c8f1d73a2df55c6dc967d600bbe6b4724983 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 14 Jul 2021 12:20:26 +0800 Subject: [PATCH 086/179] Exec with time arg now will obey time zone settings on engine (#1989) Fix #1770 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1989 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dialect.go | 12 ++++-------- dialects/mssql.go | 13 +++++++++++++ dialects/mysql.go | 15 +++++++++++++++ dialects/oracle.go | 15 +++++++++++++++ dialects/postgres.go | 20 +++++++++++++++----- dialects/sqlite3.go | 15 +++++++++++++-- integrations/session_raw_test.go | 30 ++++++++++++++++++++++++++++++ internal/statements/statement.go | 17 +++++++++++++++-- scan.go | 22 ++++++++++++++++++---- session_query.go | 2 +- 10 files changed, 139 insertions(+), 22 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index b3d374cc..df33155d 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -42,11 +42,12 @@ func (uri *URI) SetSchema(schema string) { type Dialect interface { Init(*URI) error URI() *URI - SQLType(*schemas.Column) string - Alias(string) string // return what a sql type's alias of - FormatBytes(b []byte) string Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) + SQLType(*schemas.Column) string + Alias(string) string // return what a sql type's alias of + ColumnTypeKind(string) int // database column type kind + IsReserved(string) bool Quoter() schemas.Quoter SetQuotePolicy(quotePolicy QuotePolicy) @@ -102,11 +103,6 @@ func (db *Base) URI() *URI { return db.uri } -// FormatBytes formats bytes -func (db *Base) FormatBytes(bs []byte) string { - return fmt.Sprintf("0x%x", bs) -} - // DropTableSQL returns drop table SQL func (db *Base) DropTableSQL(tableName string) (string, bool) { quote := db.dialect.Quoter().Quote diff --git a/dialects/mssql.go b/dialects/mssql.go index c3c15077..e708ba80 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -364,6 +364,19 @@ func (db *mssql) SQLType(c *schemas.Column) string { return res } +func (db *mssql) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATE", "DATETIME", "DATETIME2", "TIME": + return schemas.TIME_TYPE + case "VARCHAR", "TEXT", "CHAR", "NVARCHAR", "NCHAR", "NTEXT": + return schemas.TEXT_TYPE + case "FLOAT", "REAL", "BIGINT", "DATETIMEOFFSET", "TINYINT", "SMALLINT", "INT": + return schemas.NUMERIC_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + func (db *mssql) IsReserved(name string) bool { _, ok := mssqlReservedWords[strings.ToUpper(name)] return ok diff --git a/dialects/mysql.go b/dialects/mysql.go index da19b820..db45cd62 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -337,6 +337,21 @@ func (db *mysql) SQLType(c *schemas.Column) string { return res } +func (db *mysql) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATETIME": + return schemas.TIME_TYPE + case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET": + return schemas.TEXT_TYPE + case "BIGINT", "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "FLOAT", "REAL", "DOUBLE PRECISION", "DECIMAL", "NUMERIC", "BIT": + return schemas.NUMERIC_TYPE + case "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB": + return schemas.BLOB_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + func (db *mysql) IsReserved(name string) bool { _, ok := mysqlReservedWords[strings.ToUpper(name)] return ok diff --git a/dialects/oracle.go b/dialects/oracle.go index 7043972b..5dd92887 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -568,6 +568,21 @@ func (db *oracle) SQLType(c *schemas.Column) string { return res } +func (db *oracle) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATE": + return schemas.TIME_TYPE + case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": + return schemas.TEXT_TYPE + case "NUMBER": + return schemas.NUMERIC_TYPE + case "BLOB": + return schemas.BLOB_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + func (db *oracle) AutoIncrStr() string { return "AUTO_INCREMENT" } diff --git a/dialects/postgres.go b/dialects/postgres.go index 9f3c7275..4ec780e8 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -873,11 +873,6 @@ func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { } } -// FormatBytes formats bytes -func (db *postgres) FormatBytes(bs []byte) string { - return fmt.Sprintf("E'\\x%x'", bs) -} - func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -943,6 +938,21 @@ func (db *postgres) SQLType(c *schemas.Column) string { return res } +func (db *postgres) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATETIME", "TIMESTAMP": + return schemas.TIME_TYPE + case "VARCHAR", "TEXT": + return schemas.TEXT_TYPE + case "BIGINT", "BIGSERIAL", "SMALLINT", "INT", "INT8", "INT4", "INTEGER", "SERIAL", "FLOAT", "FLOAT4", "REAL", "DOUBLE PRECISION": + return schemas.NUMERIC_TYPE + case "BOOL": + return schemas.BOOL_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + func (db *postgres) IsReserved(name string) bool { _, ok := postgresReservedWords[strings.ToUpper(name)] return ok diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 04e5b457..581272ad 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -233,8 +233,19 @@ func (db *sqlite3) SQLType(c *schemas.Column) string { } } -func (db *sqlite3) FormatBytes(bs []byte) string { - return fmt.Sprintf("X'%x'", bs) +func (db *sqlite3) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATETIME": + return schemas.TIME_TYPE + case "TEXT": + return schemas.TEXT_TYPE + case "INTEGER", "REAL", "NUMERIC", "DECIMAL": + return schemas.NUMERIC_TYPE + case "BLOB": + return schemas.BLOB_TYPE + default: + return schemas.UNKNOW_TYPE + } } func (db *sqlite3) IsReserved(name string) bool { diff --git a/integrations/session_raw_test.go b/integrations/session_raw_test.go index 8b9d6766..36677683 100644 --- a/integrations/session_raw_test.go +++ b/integrations/session_raw_test.go @@ -7,6 +7,7 @@ package integrations import ( "strconv" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -35,3 +36,32 @@ func TestExecAndQuery(t *testing.T) { assert.EqualValues(t, 1, id) assert.Equal(t, "user", string(results[0]["name"])) } + +func TestExecTime(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type UserinfoExecTime struct { + Uid int + Name string + Created time.Time + } + + assert.NoError(t, testEngine.Sync2(new(UserinfoExecTime))) + now := time.Now() + res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_exec_time`", true)+" (uid, name, created) VALUES (?, ?, ?)", 1, "user", now) + assert.NoError(t, err) + cnt, err := res.RowsAffected() + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + results, err := testEngine.QueryString("SELECT * FROM " + testEngine.TableName("`userinfo_exec_time`", true)) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, now.In(testEngine.GetTZLocation()).Format("2006-01-02 15:04:05"), results[0]["created"]) + + var uet UserinfoExecTime + has, err := testEngine.Where("uid=?", 1).Get(&uet) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, now.In(testEngine.GetTZLocation()).Format("2006-01-02 15:04:05"), uet.Created.Format("2006-01-02 15:04:05")) +} diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 2d173b87..bfe9987f 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -942,16 +942,29 @@ func (statement *Statement) quoteColumnStr(columnStr string) string { // ConvertSQLOrArgs converts sql or args func (statement *Statement) ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { - sql, args, err := convertSQLOrArgs(sqlOrArgs...) + sql, args, err := statement.convertSQLOrArgs(sqlOrArgs...) if err != nil { return "", nil, err } return statement.ReplaceQuote(sql), args, nil } -func convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { +func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { switch sqlOrArgs[0].(type) { case string: + if len(sqlOrArgs) > 1 { + var newArgs = make([]interface{}, 0, len(sqlOrArgs)-1) + for _, arg := range sqlOrArgs[1:] { + if v, ok := arg.(*time.Time); ok { + newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05")) + } else if v, ok := arg.(time.Time); ok { + newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05")) + } else { + newArgs = append(newArgs, arg) + } + } + return sqlOrArgs[0].(string), newArgs, nil + } return sqlOrArgs[0].(string), sqlOrArgs[1:], nil case *builder.Builder: return sqlOrArgs[0].(*builder.Builder).ToSQL() diff --git a/scan.go b/scan.go index d668208a..2fedd415 100644 --- a/scan.go +++ b/scan.go @@ -14,6 +14,7 @@ import ( "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/dialects" + "xorm.io/xorm/schemas" ) // genScanResultsByBeanNullabale generates scan result @@ -123,7 +124,7 @@ func genScanResultsByBean(bean interface{}) (interface{}, bool, error) { } } -func row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { +func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { var scanResults = make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { var s sql.NullString @@ -135,9 +136,22 @@ func row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[ } result := make(map[string]string, len(fields)) - for ii, key := range fields { - s := scanResults[ii].(*sql.NullString) - result[key] = s.String + for i, key := range fields { + s := scanResults[i].(*sql.NullString) + if s.String == "" { + result[key] = "" + continue + } + + if schemas.TIME_TYPE == engine.dialect.ColumnTypeKind(types[i].DatabaseTypeName()) { + t, err := convert.String2Time(s.String, engine.DatabaseTZ, engine.TZLocation) + if err != nil { + return nil, err + } + result[key] = t.Format("2006-01-02 15:04:05") + } else { + result[key] = s.String + } } return result, nil } diff --git a/session_query.go b/session_query.go index fa33496d..d14c3908 100644 --- a/session_query.go +++ b/session_query.go @@ -33,7 +33,7 @@ func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string } for rows.Next() { - result, err := row2mapStr(rows, types, fields) + result, err := session.engine.row2mapStr(rows, types, fields) if err != nil { return nil, err } From 69a7db5312a1e6ef8c1edbf80127a9fb44a37cff Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 14 Jul 2021 17:06:53 +0800 Subject: [PATCH 087/179] improve uint tests (#1990) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1990 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_pk_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/integrations/session_pk_test.go b/integrations/session_pk_test.go index d5f23491..8f7dcb55 100644 --- a/integrations/session_pk_test.go +++ b/integrations/session_pk_test.go @@ -173,6 +173,16 @@ func TestUintId(t *testing.T) { err = testEngine.CreateTables(&UintId{}) assert.NoError(t, err) + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + assert.EqualValues(t, 1, len(tables)) + cols := tables[0].PKColumns() + assert.EqualValues(t, 1, len(cols)) + if testEngine.Dialect().URI().DBType == schemas.MYSQL { + assert.EqualValues(t, "UNSIGNED INT", cols[0].SQLType.Name) + } + cnt, err := testEngine.Insert(&UintId{Name: "test"}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) From 779a74ccff7e50341c48eb59d7fd77330a74d5b5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 15 Jul 2021 07:06:15 +0800 Subject: [PATCH 088/179] Remove duplicated code (#1991) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1991 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- session_get.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/session_get.go b/session_get.go index f710a0b1..cc6427d7 100644 --- a/session_get.go +++ b/session_get.go @@ -268,12 +268,6 @@ func (session *Session) getVars(rows *core.Rows, types []*sql.ColumnType, fields } func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) (bool, error) { - fields, err := rows.Columns() - if err != nil { - // WARN: Alougth rows return true, but get fields failed - return true, err - } - scanResults, err := session.row2Slice(rows, fields, bean) if err != nil { return false, err From aaa2111e8ff6340b497b6a991d6d71e9b45282bc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 19 Jul 2021 00:21:46 +0800 Subject: [PATCH 089/179] Refactor asbytes (#1995) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1995 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 197 ++++++++++---- convert/time.go | 9 +- dialects/mysql.go | 47 ---- engine.go | 7 +- integrations/session_get_test.go | 2 +- integrations/time_test.go | 45 +++ integrations/types_null_test.go | 14 +- rows.go | 6 +- scan.go | 32 +-- session.go | 167 ++++-------- session_convert.go | 451 ------------------------------- session_find.go | 7 +- session_get.go | 10 +- session_insert.go | 6 +- session_raw.go | 60 ---- 15 files changed, 296 insertions(+), 764 deletions(-) diff --git a/convert.go b/convert.go index 69277734..533dbe99 100644 --- a/convert.go +++ b/convert.go @@ -7,6 +7,7 @@ package xorm import ( "database/sql" "database/sql/driver" + "encoding/json" "errors" "fmt" "math/big" @@ -285,23 +286,94 @@ func asBigFloat(src interface{}) (*big.Float, error) { return nil, fmt.Errorf("unsupported value %T as big.Float", src) } -func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { +func asBytes(src interface{}) ([]byte, bool) { + switch t := src.(type) { + case []byte: + return t, true + case *sql.NullString: + if !t.Valid { + return nil, true + } + return []byte(t.String), true + case *sql.RawBytes: + return *t, true + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.AppendInt(buf, rv.Int(), 10), true + return strconv.AppendInt(nil, rv.Int(), 10), true case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.AppendUint(buf, rv.Uint(), 10), true + return strconv.AppendUint(nil, rv.Uint(), 10), true case reflect.Float32: - return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true + return strconv.AppendFloat(nil, rv.Float(), 'g', -1, 32), true case reflect.Float64: - return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true + return strconv.AppendFloat(nil, rv.Float(), 'g', -1, 64), true case reflect.Bool: - return strconv.AppendBool(buf, rv.Bool()), true + return strconv.AppendBool(nil, rv.Bool()), true case reflect.String: - s := rv.String() - return append(buf, s...), true + return []byte(rv.String()), true } - return + return nil, false +} + +func asTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.Time, error) { + switch t := src.(type) { + case string: + return convert.String2Time(t, dbLoc, uiLoc) + case *sql.NullString: + if !t.Valid { + return nil, nil + } + return convert.String2Time(t.String, dbLoc, uiLoc) + case []uint8: + if t == nil { + return nil, nil + } + return convert.String2Time(string(t), dbLoc, uiLoc) + case *sql.NullTime: + if !t.Valid { + return nil, nil + } + z, _ := t.Time.Zone() + if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() { + tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(), + t.Time.Minute(), t.Time.Second(), t.Time.Nanosecond(), dbLoc).In(uiLoc) + return &tm, nil + } + tm := t.Time.In(uiLoc) + return &tm, nil + case *time.Time: + z, _ := t.Zone() + if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { + tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t.Minute(), t.Second(), t.Nanosecond(), dbLoc).In(uiLoc) + return &tm, nil + } + tm := t.In(uiLoc) + return &tm, nil + case time.Time: + z, _ := t.Zone() + if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { + tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t.Minute(), t.Second(), t.Nanosecond(), dbLoc).In(uiLoc) + return &tm, nil + } + tm := t.In(uiLoc) + return &tm, nil + case int: + tm := time.Unix(int64(t), 0).In(uiLoc) + return &tm, nil + case int64: + tm := time.Unix(t, 0).In(uiLoc) + return &tm, nil + case *sql.NullInt64: + tm := time.Unix(t.Int64, 0).In(uiLoc) + return &tm, nil + + } + return nil, fmt.Errorf("unsupported value %#v as time", src) } // convertAssign copies to dest the value in src, converting it if possible. @@ -559,8 +631,7 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve return nil } case *[]byte: - sv = reflect.ValueOf(src) - if b, ok := asBytes(nil, sv); ok { + if b, ok := asBytes(src); ok { *d = b return nil } @@ -575,44 +646,24 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve return nil } - return convertAssignV(reflect.ValueOf(dest), src, originalLocation, convertedLocation) + return convertAssignV(reflect.ValueOf(dest), src) } -func convertAssignV(dpv reflect.Value, src interface{}, originalLocation, convertedLocation *time.Location) error { - if dpv.Kind() != reflect.Ptr { - return errors.New("destination not a pointer") - } - if dpv.IsNil() { - return errNilPtr - } - - var sv = reflect.ValueOf(src) - - dv := reflect.Indirect(dpv) - if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) { - switch b := src.(type) { - case []byte: - dv.Set(reflect.ValueOf(cloneBytes(b))) - default: - dv.Set(sv) - } +func convertAssignV(dv reflect.Value, src interface{}) error { + if src == nil { return nil } - if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) { - dv.Set(sv.Convert(dv.Type())) - return nil + if dv.Type().Implements(scannerType) { + return dv.Interface().(sql.Scanner).Scan(src) } switch dv.Kind() { case reflect.Ptr: - if src == nil { - dv.Set(reflect.Zero(dv.Type())) - return nil + if dv.IsNil() { + dv.Set(reflect.New(dv.Type().Elem())) } - - dv.Set(reflect.New(dv.Type().Elem())) - return convertAssign(dv.Interface(), src, originalLocation, convertedLocation) + return convertAssignV(dv.Elem(), src) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i64, err := asInt64(src) if err != nil { @@ -640,9 +691,28 @@ func convertAssignV(dpv reflect.Value, src interface{}, originalLocation, conver case reflect.String: dv.SetString(asString(src)) return nil + case reflect.Bool: + b, err := asBool(src) + if err != nil { + return err + } + dv.SetBool(b) + return nil + case reflect.Slice, reflect.Map, reflect.Struct, reflect.Array: + data, ok := asBytes(src) + if !ok { + return fmt.Errorf("onvertAssignV: src cannot be as bytes %#v", src) + } + if data == nil { + return nil + } + if dv.Kind() != reflect.Ptr { + dv = dv.Addr() + } + return json.Unmarshal(data, dv.Interface()) + default: + return fmt.Errorf("convertAssignV: unsupported Scan, storing driver.Value type %T into type %T", src, dv.Interface()) } - - return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dpv.Interface()) } func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { @@ -682,16 +752,43 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) } -func asBool(bs []byte) (bool, error) { - if len(bs) == 0 { - return false, nil +func asBool(src interface{}) (bool, error) { + switch v := src.(type) { + case bool: + return v, nil + case *bool: + return *v, nil + case *sql.NullBool: + return v.Bool, nil + case int64: + return v > 0, nil + case int: + return v > 0, nil + case int8: + return v > 0, nil + case int16: + return v > 0, nil + case int32: + return v > 0, nil + case []byte: + if len(v) == 0 { + return false, nil + } + if v[0] == 0x00 { + return false, nil + } else if v[0] == 0x01 { + return true, nil + } + return strconv.ParseBool(string(v)) + case string: + return strconv.ParseBool(v) + case *sql.NullInt64: + return v.Int64 > 0, nil + case *sql.NullInt32: + return v.Int32 > 0, nil + default: + return false, fmt.Errorf("unknow type %T as bool", src) } - if bs[0] == 0x00 { - return false, nil - } else if bs[0] == 0x01 { - return true, nil - } - return strconv.ParseBool(string(bs)) } // str2PK convert string value to primary key value according to tp diff --git a/convert/time.go b/convert/time.go index 696b301c..5a3e5246 100644 --- a/convert/time.go +++ b/convert/time.go @@ -6,6 +6,7 @@ package convert import ( "fmt" + "strconv" "time" ) @@ -19,7 +20,7 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t dt = dt.In(convertedLocation) return &dt, nil } else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' { - dt, err := time.ParseInLocation(time.RFC3339, s, originalLocation) + dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation) if err != nil { return nil, err } @@ -32,6 +33,12 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t } dt = dt.In(convertedLocation) return &dt, nil + } else { + i, err := strconv.ParseInt(s, 10, 64) + if err == nil { + tm := time.Unix(i, 0).In(convertedLocation) + return &tm, nil + } } return nil, fmt.Errorf("unsupported convertion from %s to time", s) } diff --git a/dialects/mysql.go b/dialects/mysql.go index db45cd62..9312c071 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -15,7 +15,6 @@ import ( "strings" "time" - "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/schemas" ) @@ -733,52 +732,6 @@ func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) { } } -func (p *mysqlDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, scanResults ...interface{}) error { - var v2 = make([]interface{}, 0, len(scanResults)) - var turnBackIdxes = make([]int, 0, 5) - for i, vv := range scanResults { - switch vv.(type) { - case *time.Time: - v2 = append(v2, &sql.NullString{}) - turnBackIdxes = append(turnBackIdxes, i) - case *sql.NullTime: - v2 = append(v2, &sql.NullString{}) - turnBackIdxes = append(turnBackIdxes, i) - default: - v2 = append(v2, scanResults[i]) - } - } - if err := rows.Scan(v2...); err != nil { - return err - } - for _, i := range turnBackIdxes { - switch t := scanResults[i].(type) { - case *time.Time: - var s = *(v2[i].(*sql.NullString)) - if !s.Valid { - break - } - dt, err := convert.String2Time(s.String, ctx.DBLocation, ctx.UserLocation) - if err != nil { - return err - } - *t = *dt - case *sql.NullTime: - var s = *(v2[i].(*sql.NullString)) - if !s.Valid { - break - } - dt, err := convert.String2Time(s.String, ctx.DBLocation, ctx.UserLocation) - if err != nil { - return err - } - t.Time = *dt - t.Valid = true - } - } - return nil -} - type mymysqlDriver struct { mysqlDriver } diff --git a/engine.go b/engine.go index d3ee8a8c..b4ef9593 100644 --- a/engine.go +++ b/engine.go @@ -543,6 +543,11 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } + fields, err := rows.Columns() + if err != nil { + return err + } + sess := engine.NewSession() defer sess.Close() for rows.Next() { @@ -551,7 +556,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } - scanResults, err := sess.engine.scanStringInterface(rows, types) + scanResults, err := sess.engine.scanStringInterface(rows, fields, types) if err != nil { return err } diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index ca894d59..b1dffe14 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -914,7 +914,7 @@ func TestGetTime(t *testing.T) { assertSync(t, new(GetTimeStruct)) var gts = GetTimeStruct{ - CreateTime: time.Now(), + CreateTime: time.Now().In(testEngine.GetTZLocation()), } _, err := testEngine.Insert(>s) assert.NoError(t, err) diff --git a/integrations/time_test.go b/integrations/time_test.go index 6d8d812c..50fd1847 100644 --- a/integrations/time_test.go +++ b/integrations/time_test.go @@ -53,9 +53,18 @@ func TestTimeUserTimeDiffLoc(t *testing.T) { assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) + oldTZLoc := testEngine.GetTZLocation() + defer func() { + testEngine.SetTZLocation(oldTZLoc) + }() testEngine.SetTZLocation(loc) + dbLoc, err := time.LoadLocation("America/New_York") assert.NoError(t, err) + oldDBLoc := testEngine.GetTZDatabase() + defer func() { + testEngine.SetTZDatabase(oldDBLoc) + }() testEngine.SetTZDatabase(dbLoc) type TimeUser2 struct { @@ -118,9 +127,18 @@ func TestTimeUserCreatedDiffLoc(t *testing.T) { assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) + oldTZLoc := testEngine.GetTZLocation() + defer func() { + testEngine.SetTZLocation(oldTZLoc) + }() testEngine.SetTZLocation(loc) + dbLoc, err := time.LoadLocation("America/New_York") assert.NoError(t, err) + oldDBLoc := testEngine.GetTZDatabase() + defer func() { + testEngine.SetTZDatabase(oldDBLoc) + }() testEngine.SetTZDatabase(dbLoc) type UserCreated2 struct { @@ -204,9 +222,18 @@ func TestTimeUserUpdatedDiffLoc(t *testing.T) { assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) + oldTZLoc := testEngine.GetTZLocation() + defer func() { + testEngine.SetTZLocation(oldTZLoc) + }() testEngine.SetTZLocation(loc) + dbLoc, err := time.LoadLocation("America/New_York") assert.NoError(t, err) + oldDBLoc := testEngine.GetTZDatabase() + defer func() { + testEngine.SetTZDatabase(oldDBLoc) + }() testEngine.SetTZDatabase(dbLoc) type UserUpdated2 struct { @@ -311,9 +338,18 @@ func TestTimeUserDeletedDiffLoc(t *testing.T) { assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) + oldTZLoc := testEngine.GetTZLocation() + defer func() { + testEngine.SetTZLocation(oldTZLoc) + }() testEngine.SetTZLocation(loc) + dbLoc, err := time.LoadLocation("America/New_York") assert.NoError(t, err) + oldDBLoc := testEngine.GetTZDatabase() + defer func() { + testEngine.SetTZDatabase(oldDBLoc) + }() testEngine.SetTZDatabase(dbLoc) type UserDeleted2 struct { @@ -435,9 +471,18 @@ func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) + oldTZLoc := testEngine.GetTZLocation() + defer func() { + testEngine.SetTZLocation(oldTZLoc) + }() testEngine.SetTZLocation(loc) + dbLoc, err := time.LoadLocation("America/New_York") assert.NoError(t, err) + oldDBLoc := testEngine.GetTZDatabase() + defer func() { + testEngine.SetTZDatabase(oldDBLoc) + }() testEngine.SetTZDatabase(dbLoc) type UserDeleted4 struct { diff --git a/integrations/types_null_test.go b/integrations/types_null_test.go index 98bd86b9..86ce1939 100644 --- a/integrations/types_null_test.go +++ b/integrations/types_null_test.go @@ -7,7 +7,6 @@ package integrations import ( "database/sql" "database/sql/driver" - "errors" "fmt" "strconv" "strings" @@ -42,15 +41,22 @@ func (m *CustomStruct) Scan(value interface{}) error { return nil } - if s, ok := value.([]byte); ok { - seps := strings.Split(string(s), "/") + var s string + switch t := value.(type) { + case string: + s = t + case []byte: + s = string(t) + } + if len(s) > 0 { + seps := strings.Split(s, "/") m.Year, _ = strconv.Atoi(seps[0]) m.Month, _ = strconv.Atoi(seps[1]) m.Day, _ = strconv.Atoi(seps[2]) return nil } - return errors.New("scan data not fit []byte") + return fmt.Errorf("scan data %#v not fit []byte", value) } func (m CustomStruct) Value() (driver.Value, error) { diff --git a/rows.go b/rows.go index a56ea1c9..5e0a1ffe 100644 --- a/rows.go +++ b/rows.go @@ -129,8 +129,12 @@ func (rows *Rows) Scan(bean interface{}) error { if err != nil { return err } + types, err := rows.rows.ColumnTypes() + if err != nil { + return err + } - scanResults, err := rows.session.row2Slice(rows.rows, fields, bean) + scanResults, err := rows.session.row2Slice(rows.rows, fields, types, bean) if err != nil { return err } diff --git a/scan.go b/scan.go index 2fedd415..e4c0e4a1 100644 --- a/scan.go +++ b/scan.go @@ -20,6 +20,8 @@ import ( // genScanResultsByBeanNullabale generates scan result func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { switch t := bean.(type) { + case *interface{}: + return t, false, nil case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes: return t, false, nil case *time.Time: @@ -71,7 +73,10 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { func genScanResultsByBean(bean interface{}) (interface{}, bool, error) { switch t := bean.(type) { + case *interface{}: + return t, false, nil case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, + *sql.RawBytes, *string, *int, *int8, *int16, *int32, *int64, *uint, *uint8, *uint16, *uint32, *uint64, @@ -175,17 +180,14 @@ func row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (ma return result, nil } -func (engine *Engine) scanStringInterface(rows *core.Rows, types []*sql.ColumnType) ([]interface{}, error) { +func (engine *Engine) scanStringInterface(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) { var scanResults = make([]interface{}, len(types)) for i := 0; i < len(types); i++ { var s sql.NullString scanResults[i] = &s } - if err := engine.driver.Scan(&dialects.ScanContext{ - DBLocation: engine.DatabaseTZ, - UserLocation: engine.TZLocation, - }, rows, types, scanResults...); err != nil { + if err := engine.scan(rows, fields, types, scanResults...); err != nil { return nil, err } return scanResults, nil @@ -200,14 +202,14 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column var replaced bool var scanResult interface{} switch t := v.(type) { + case *big.Float, *time.Time, *sql.NullTime: + scanResult = &sql.NullString{} + replaced = true case sql.Scanner: scanResult = t case convert.Conversion: scanResult = &sql.RawBytes{} replaced = true - case *big.Float: - scanResult = &sql.NullString{} - replaced = true default: var useNullable = true if engine.driver.Features().SupportNullable { @@ -246,7 +248,7 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column return nil } -func (engine *Engine) scanInterfaces(rows *core.Rows, types []*sql.ColumnType) ([]interface{}, error) { +func (engine *Engine) scanInterfaces(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) { var scanResultContainers = make([]interface{}, len(types)) for i := 0; i < len(types); i++ { scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName()) @@ -255,17 +257,14 @@ func (engine *Engine) scanInterfaces(rows *core.Rows, types []*sql.ColumnType) ( } scanResultContainers[i] = scanResult } - if err := engine.driver.Scan(&dialects.ScanContext{ - DBLocation: engine.DatabaseTZ, - UserLocation: engine.TZLocation, - }, rows, types, scanResultContainers...); err != nil { + if err := engine.scan(rows, fields, types, scanResultContainers...); err != nil { return nil, err } return scanResultContainers, nil } func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { - scanResults, err := engine.scanStringInterface(rows, types) + scanResults, err := engine.scanStringInterface(rows, fields, types) if err != nil { return nil, err } @@ -307,10 +306,7 @@ func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, } scanResultContainers[i] = scanResult } - if err := engine.driver.Scan(&dialects.ScanContext{ - DBLocation: engine.DatabaseTZ, - UserLocation: engine.TZLocation, - }, rows, types, scanResultContainers...); err != nil { + if err := engine.scan(rows, fields, types, scanResultContainers...); err != nil { return nil, err } diff --git a/session.go b/session.go index 486911a5..5557d717 100644 --- a/session.go +++ b/session.go @@ -16,7 +16,6 @@ import ( "io" "reflect" "strings" - "time" "xorm.io/xorm/contexts" "xorm.io/xorm/convert" @@ -389,7 +388,7 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *s // Cell cell is a result of one column field type Cell *interface{} -func (session *Session) rows2Beans(rows *core.Rows, fields []string, +func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sql.ColumnType, table *schemas.Table, newElemFunc func([]string) reflect.Value, sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { for rows.Next() { @@ -398,7 +397,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, dataStruct := newValue.Elem() // handle beforeClosures - scanResults, err := session.row2Slice(rows, fields, bean) + scanResults, err := session.row2Slice(rows, fields, types, bean) if err != nil { return err } @@ -417,7 +416,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, return nil } -func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interface{}) ([]interface{}, error) { +func (session *Session) row2Slice(rows *core.Rows, fields []string, types []*sql.ColumnType, bean interface{}) ([]interface{}, error) { for _, closure := range session.beforeClosures { closure(bean) } @@ -427,7 +426,7 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa var cell interface{} scanResults[i] = &cell } - if err := rows.Scan(scanResults...); err != nil { + if err := session.engine.scan(rows, fields, types, scanResults...); err != nil { return nil, err } @@ -454,27 +453,28 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec if fieldValue.CanAddr() { if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - data, err := value2Bytes(&rawValue) - if err != nil { - return err + data, ok := asBytes(scanResult) + if !ok { + return fmt.Errorf("cannot convert %#v as bytes", scanResult) } - if err := structConvert.FromDB(data); err != nil { - return err - } - return nil + return structConvert.FromDB(data) } } - if _, ok := fieldValue.Interface().(convert.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) - } - fieldValue.Interface().(convert.Conversion).FromDB(data) - } else { - return err + if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { + data, ok := asBytes(scanResult) + if !ok { + return fmt.Errorf("cannot convert %#v as bytes", scanResult) } - return nil + if data == nil { + return nil + } + + if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + return fieldValue.Interface().(convert.Conversion).FromDB(data) + } + return structConvert.FromDB(data) } rawValueType := reflect.TypeOf(rawValue.Interface()) @@ -554,64 +554,28 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } return nil case reflect.Slice, reflect.Array: - switch rawValueType.Kind() { - case reflect.Slice, reflect.Array: - switch rawValueType.Elem().Kind() { - case reflect.Uint8: - if fieldType.Elem().Kind() == reflect.Uint8 { - if col.SQLType.IsText() { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } else { - if fieldValue.Len() > 0 { - for i := 0; i < fieldValue.Len(); i++ { - if i < vv.Len() { - fieldValue.Index(i).Set(vv.Index(i)) - } - } - } else { - for i := 0; i < vv.Len(); i++ { - fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) - } + bs, ok := asBytes(scanResult) + if ok && fieldType.Elem().Kind() == reflect.Uint8 { + if col.SQLType.IsText() { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } else { + if fieldValue.Len() > 0 { + for i := 0; i < fieldValue.Len(); i++ { + if i < vv.Len() { + fieldValue.Index(i).Set(vv.Index(i)) } } - return nil + } else { + for i := 0; i < vv.Len(); i++ { + fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) + } } } - } - case reflect.String: - if rawValueType.Kind() == reflect.String { - fieldValue.SetString(vv.String()) - return nil - } - case reflect.Bool: - if rawValueType.Kind() == reflect.Bool { - fieldValue.SetBool(vv.Bool()) - return nil - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch rawValueType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - fieldValue.SetInt(vv.Int()) - return nil - } - case reflect.Float32, reflect.Float64: - switch rawValueType.Kind() { - case reflect.Float32, reflect.Float64: - fieldValue.SetFloat(vv.Float()) - return nil - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - switch rawValueType.Kind() { - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - fieldValue.SetUint(vv.Uint()) - return nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - fieldValue.SetUint(uint64(vv.Int())) return nil } case reflect.Struct: @@ -630,47 +594,13 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec dbTZ = col.TimeZone } - if rawValueType == schemas.TimeType { - t := vv.Convert(schemas.TimeType).Interface().(time.Time) - - z, _ := t.Zone() - // set new location if database don't save timezone or give an incorrect timezone - if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location - session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", col.Name, t, z, *t.Location()) - t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), - t.Minute(), t.Second(), t.Nanosecond(), dbTZ) - } - - t = t.In(session.engine.TZLocation) - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - return nil - } else if rawValueType == schemas.IntType || rawValueType == schemas.Int64Type || - rawValueType == schemas.Int32Type { - t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation) - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - return nil - } else { - if d, ok := vv.Interface().([]uint8); ok { - t, err := session.byte2Time(col, d) - if err != nil { - session.engine.logger.Errorf("byte2Time error: %v", err) - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - return nil - } - - } else if d, ok := vv.Interface().(string); ok { - t, err := session.str2Time(col, d) - if err != nil { - session.engine.logger.Errorf("byte2Time error: %v", err) - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - return nil - } - } else { - return fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface()) - } + t, err := asTime(scanResult, dbTZ, session.engine.TZLocation) + if err != nil { + return err } + + fieldValue.Set(reflect.ValueOf(*t).Convert(fieldType)) + return nil } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { err := nulVal.Scan(vv.Interface()) if err == nil { @@ -733,12 +663,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } } // switch fieldType.Kind() - data, err := value2Bytes(&rawValue) - if err != nil { - return err - } - - return session.bytes2Value(col, fieldValue, data) + return convertAssignV(fieldValue.Addr(), scanResult) } func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { diff --git a/session_convert.go b/session_convert.go index b8218a77..452801e2 100644 --- a/session_convert.go +++ b/session_convert.go @@ -5,16 +5,11 @@ package xorm import ( - "database/sql" - "errors" "fmt" - "reflect" "strconv" "strings" "time" - "xorm.io/xorm/convert" - "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -73,449 +68,3 @@ func (session *Session) str2Time(col *schemas.Column, data string) (outTime time outTime = x.In(session.engine.TZLocation) return } - -func (session *Session) byte2Time(col *schemas.Column, data []byte) (outTime time.Time, outErr error) { - return session.str2Time(col, string(data)) -} - -// convert a db data([]byte) to a field value -func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Value, data []byte) error { - if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - return structConvert.FromDB(data) - } - - if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { - return structConvert.FromDB(data) - } - - var v interface{} - key := col.Name - fieldType := fieldValue.Type() - - switch fieldType.Kind() { - case reflect.Complex64, reflect.Complex128: - x := reflect.New(fieldType) - if len(data) > 0 { - err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - case reflect.Slice, reflect.Array, reflect.Map: - v = data - t := fieldType.Elem() - k := t.Kind() - if col.SQLType.IsText() { - x := reflect.New(fieldType) - if len(data) > 0 { - err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - } else if col.SQLType.IsBlob() { - if k == reflect.Uint8 { - fieldValue.Set(reflect.ValueOf(v)) - } else { - x := reflect.New(fieldType) - if len(data) > 0 { - err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - } - } else { - return ErrUnSupportedType - } - case reflect.String: - fieldValue.SetString(string(data)) - case reflect.Bool: - v, err := asBool(data) - if err != nil { - return fmt.Errorf("arg %v as bool: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(v)) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - sdata := string(data) - var x int64 - var err error - // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == schemas.Bit && - session.engine.dialect.URI().DBType == schemas.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API - if len(data) == 1 { - x = int64(data[0]) - } else { - x = 0 - } - } else if strings.HasPrefix(sdata, "0x") { - x, err = strconv.ParseInt(sdata, 16, 64) - } else if strings.HasPrefix(sdata, "0") { - x, err = strconv.ParseInt(sdata, 8, 64) - } else if strings.EqualFold(sdata, "true") { - x = 1 - } else if strings.EqualFold(sdata, "false") { - x = 0 - } else { - x, err = strconv.ParseInt(sdata, 10, 64) - } - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.SetInt(x) - case reflect.Float32, reflect.Float64: - x, err := strconv.ParseFloat(string(data), 64) - if err != nil { - return fmt.Errorf("arg %v as float64: %s", key, err.Error()) - } - fieldValue.SetFloat(x) - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - x, err := strconv.ParseUint(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.SetUint(x) - //Currently only support Time type - case reflect.Struct: - // !! 增加支持sql.Scanner接口的结构,如sql.NullString - if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { - if err := nulVal.Scan(data); err != nil { - return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error()) - } - } else { - if fieldType.ConvertibleTo(schemas.TimeType) { - x, err := session.byte2Time(col, data) - if err != nil { - return err - } - v = x - fieldValue.Set(reflect.ValueOf(v).Convert(fieldType)) - } else if session.statement.UseCascade { - table, err := session.engine.tagParser.ParseWithCache(*fieldValue) - if err != nil { - return err - } - - // TODO: current only support 1 primary key - if len(table.PrimaryKeys) > 1 { - return errors.New("unsupported composited primary key cascade") - } - - var pk = make(schemas.PK, len(table.PrimaryKeys)) - rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) - pk[0], err = str2PK(string(data), rawValueType) - if err != nil { - return err - } - - if !pk.IsZero() { - // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch - // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne - // property to be fetched lazily - structInter := reflect.New(fieldValue.Type()) - has, err := session.ID(pk).NoCascade().get(structInter.Interface()) - if err != nil { - return err - } - if has { - v = structInter.Elem().Interface() - fieldValue.Set(reflect.ValueOf(v)) - } else { - return errors.New("cascade obj is not exist") - } - } - } - } - case reflect.Ptr: - // !nashtsai! TODO merge duplicated codes above - //typeStr := fieldType.String() - switch fieldType.Elem().Kind() { - // case "*string": - case schemas.StringType.Kind(): - x := string(data) - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*bool": - case schemas.BoolType.Kind(): - d := string(data) - v, err := strconv.ParseBool(d) - if err != nil { - return fmt.Errorf("arg %v as bool: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType)) - // case "*complex64": - case schemas.Complex64Type.Kind(): - var x complex64 - if len(data) > 0 { - err := json.DefaultJSONHandler.Unmarshal(data, &x) - if err != nil { - return err - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - } - // case "*complex128": - case schemas.Complex128Type.Kind(): - var x complex128 - if len(data) > 0 { - err := json.DefaultJSONHandler.Unmarshal(data, &x) - if err != nil { - return err - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - } - // case "*float64": - case schemas.Float64Type.Kind(): - x, err := strconv.ParseFloat(string(data), 64) - if err != nil { - return fmt.Errorf("arg %v as float64: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*float32": - case schemas.Float32Type.Kind(): - var x float32 - x1, err := strconv.ParseFloat(string(data), 32) - if err != nil { - return fmt.Errorf("arg %v as float32: %s", key, err.Error()) - } - x = float32(x1) - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*uint64": - case schemas.Uint64Type.Kind(): - var x uint64 - x, err := strconv.ParseUint(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*uint": - case schemas.UintType.Kind(): - var x uint - x1, err := strconv.ParseUint(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - x = uint(x1) - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*uint32": - case schemas.Uint32Type.Kind(): - var x uint32 - x1, err := strconv.ParseUint(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - x = uint32(x1) - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*uint8": - case schemas.Uint8Type.Kind(): - var x uint8 - x1, err := strconv.ParseUint(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - x = uint8(x1) - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*uint16": - case schemas.Uint16Type.Kind(): - var x uint16 - x1, err := strconv.ParseUint(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - x = uint16(x1) - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*int64": - case schemas.Int64Type.Kind(): - sdata := string(data) - var x int64 - var err error - // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == schemas.Bit && - strings.Contains(session.engine.DriverName(), "mysql") { - if len(data) == 1 { - x = int64(data[0]) - } else { - x = 0 - } - } else if strings.HasPrefix(sdata, "0x") { - x, err = strconv.ParseInt(sdata, 16, 64) - } else if strings.HasPrefix(sdata, "0") { - x, err = strconv.ParseInt(sdata, 8, 64) - } else { - x, err = strconv.ParseInt(sdata, 10, 64) - } - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*int": - case schemas.IntType.Kind(): - sdata := string(data) - var x int - var x1 int64 - var err error - // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == schemas.Bit && - strings.Contains(session.engine.DriverName(), "mysql") { - if len(data) == 1 { - x = int(data[0]) - } else { - x = 0 - } - } else if strings.HasPrefix(sdata, "0x") { - x1, err = strconv.ParseInt(sdata, 16, 64) - x = int(x1) - } else if strings.HasPrefix(sdata, "0") { - x1, err = strconv.ParseInt(sdata, 8, 64) - x = int(x1) - } else { - x1, err = strconv.ParseInt(sdata, 10, 64) - x = int(x1) - } - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*int32": - case schemas.Int32Type.Kind(): - sdata := string(data) - var x int32 - var x1 int64 - var err error - // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == schemas.Bit && - session.engine.dialect.URI().DBType == schemas.MYSQL { - if len(data) == 1 { - x = int32(data[0]) - } else { - x = 0 - } - } else if strings.HasPrefix(sdata, "0x") { - x1, err = strconv.ParseInt(sdata, 16, 64) - x = int32(x1) - } else if strings.HasPrefix(sdata, "0") { - x1, err = strconv.ParseInt(sdata, 8, 64) - x = int32(x1) - } else { - x1, err = strconv.ParseInt(sdata, 10, 64) - x = int32(x1) - } - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*int8": - case schemas.Int8Type.Kind(): - sdata := string(data) - var x int8 - var x1 int64 - var err error - // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == schemas.Bit && - strings.Contains(session.engine.DriverName(), "mysql") { - if len(data) == 1 { - x = int8(data[0]) - } else { - x = 0 - } - } else if strings.HasPrefix(sdata, "0x") { - x1, err = strconv.ParseInt(sdata, 16, 64) - x = int8(x1) - } else if strings.HasPrefix(sdata, "0") { - x1, err = strconv.ParseInt(sdata, 8, 64) - x = int8(x1) - } else { - x1, err = strconv.ParseInt(sdata, 10, 64) - x = int8(x1) - } - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*int16": - case schemas.Int16Type.Kind(): - sdata := string(data) - var x int16 - var x1 int64 - var err error - // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == schemas.Bit && - strings.Contains(session.engine.DriverName(), "mysql") { - if len(data) == 1 { - x = int16(data[0]) - } else { - x = 0 - } - } else if strings.HasPrefix(sdata, "0x") { - x1, err = strconv.ParseInt(sdata, 16, 64) - x = int16(x1) - } else if strings.HasPrefix(sdata, "0") { - x1, err = strconv.ParseInt(sdata, 8, 64) - x = int16(x1) - } else { - x1, err = strconv.ParseInt(sdata, 10, 64) - x = int16(x1) - } - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) - } - fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) - // case "*SomeStruct": - case reflect.Struct: - switch fieldType { - // case "*.time.Time": - case schemas.PtrTimeType: - x, err := session.byte2Time(col, data) - if err != nil { - return err - } - v = x - fieldValue.Set(reflect.ValueOf(&x)) - default: - if session.statement.UseCascade { - structInter := reflect.New(fieldType.Elem()) - table, err := session.engine.tagParser.ParseWithCache(structInter.Elem()) - if err != nil { - return err - } - - if len(table.PrimaryKeys) > 1 { - return errors.New("unsupported composited primary key cascade") - } - - var pk = make(schemas.PK, len(table.PrimaryKeys)) - rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) - pk[0], err = str2PK(string(data), rawValueType) - if err != nil { - return err - } - - if !pk.IsZero() { - // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch - // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne - // property to be fetched lazily - has, err := session.ID(pk).NoCascade().get(structInter.Interface()) - if err != nil { - return err - } - if has { - v = structInter.Interface() - fieldValue.Set(reflect.ValueOf(v)) - } else { - return errors.New("cascade obj is not exist") - } - } - } else { - return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String()) - } - } - default: - return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String()) - } - default: - return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String()) - } - - return nil -} diff --git a/session_find.go b/session_find.go index 261e6b7f..41d68479 100644 --- a/session_find.go +++ b/session_find.go @@ -172,6 +172,11 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return err } + types, err := rows.ColumnTypes() + if err != nil { + return err + } + var newElemFunc func(fields []string) reflect.Value elemType := containerValue.Type().Elem() var isPointer bool @@ -241,7 +246,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect if err != nil { return err } - err = session.rows2Beans(rows, fields, tb, newElemFunc, containerValueSetFunc) + err = session.rows2Beans(rows, fields, types, tb, newElemFunc, containerValueSetFunc) rows.Close() if err != nil { return err diff --git a/session_get.go b/session_get.go index cc6427d7..fa97e68e 100644 --- a/session_get.go +++ b/session_get.go @@ -192,7 +192,7 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) (bool, error) { switch t := bean.(type) { case *[]string: - res, err := session.engine.scanStringInterface(rows, types) + res, err := session.engine.scanStringInterface(rows, fields, types) if err != nil { return true, err } @@ -207,7 +207,7 @@ func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, field } return true, nil case *[]interface{}: - scanResults, err := session.engine.scanInterfaces(rows, types) + scanResults, err := session.engine.scanInterfaces(rows, fields, types) if err != nil { return true, err } @@ -232,7 +232,7 @@ func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, field func (session *Session) getMap(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) (bool, error) { switch t := bean.(type) { case *map[string]string: - scanResults, err := session.engine.scanStringInterface(rows, types) + scanResults, err := session.engine.scanStringInterface(rows, fields, types) if err != nil { return true, err } @@ -241,7 +241,7 @@ func (session *Session) getMap(rows *core.Rows, types []*sql.ColumnType, fields } return true, nil case *map[string]interface{}: - scanResults, err := session.engine.scanInterfaces(rows, types) + scanResults, err := session.engine.scanInterfaces(rows, fields, types) if err != nil { return true, err } @@ -268,7 +268,7 @@ func (session *Session) getVars(rows *core.Rows, types []*sql.ColumnType, fields } func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) (bool, error) { - scanResults, err := session.row2Slice(rows, fields, bean) + scanResults, err := session.row2Slice(rows, fields, types, bean) if err != nil { return false, err } diff --git a/session_insert.go b/session_insert.go index 7f8f3008..b41dbbac 100644 --- a/session_insert.go +++ b/session_insert.go @@ -375,7 +375,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - return 1, convertAssignV(aiValue.Addr(), id, session.engine.DatabaseTZ, session.engine.TZLocation) + return 1, convertAssignV(aiValue.Addr(), id) } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.URI().DBType == schemas.POSTGRES || session.engine.dialect.URI().DBType == schemas.MSSQL) { res, err := session.queryBytes(sqlStr, args...) @@ -415,7 +415,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - return 1, convertAssignV(aiValue.Addr(), id, session.engine.DatabaseTZ, session.engine.TZLocation) + return 1, convertAssignV(*aiValue, id) } res, err := session.exec(sqlStr, args...) @@ -455,7 +455,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return res.RowsAffected() } - if err := convertAssignV(aiValue.Addr(), id, session.engine.DatabaseTZ, session.engine.TZLocation); err != nil { + if err := convertAssignV(*aiValue, id); err != nil { return 0, err } diff --git a/session_raw.go b/session_raw.go index bf32c6ed..7eb8585d 100644 --- a/session_raw.go +++ b/session_raw.go @@ -6,13 +6,8 @@ package xorm import ( "database/sql" - "fmt" - "reflect" - "strconv" - "time" "xorm.io/xorm/core" - "xorm.io/xorm/schemas" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { @@ -75,61 +70,6 @@ func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row { return core.NewRow(session.queryRows(sqlStr, args...)) } -func value2String(rawValue *reflect.Value) (str string, err error) { - aa := reflect.TypeOf((*rawValue).Interface()) - vv := reflect.ValueOf((*rawValue).Interface()) - switch aa.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - str = strconv.FormatInt(vv.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - str = strconv.FormatUint(vv.Uint(), 10) - case reflect.Float32, reflect.Float64: - str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) - case reflect.String: - str = vv.String() - case reflect.Array, reflect.Slice: - switch aa.Elem().Kind() { - case reflect.Uint8: - data := rawValue.Interface().([]byte) - str = string(data) - if str == "\x00" { - str = "0" - } - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - // time type - case reflect.Struct: - if aa.ConvertibleTo(schemas.TimeType) { - str = vv.Convert(schemas.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) - } else { - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - case reflect.Bool: - str = strconv.FormatBool(vv.Bool()) - case reflect.Complex128, reflect.Complex64: - str = fmt.Sprintf("%v", vv.Complex()) - /* TODO: unsupported types below - case reflect.Map: - case reflect.Ptr: - case reflect.Uintptr: - case reflect.UnsafePointer: - case reflect.Chan, reflect.Func, reflect.Interface: - */ - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - return -} - -func value2Bytes(rawValue *reflect.Value) ([]byte, error) { - str, err := value2String(rawValue) - if err != nil { - return nil, err - } - return []byte(str), nil -} - func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[string][]byte, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { From 5950824e37b0cbbd1996bc01c62aa78e5656bb00 Mon Sep 17 00:00:00 2001 From: raizen666 Date: Mon, 19 Jul 2021 12:49:50 +0800 Subject: [PATCH 090/179] Support build flag go-json to replace default json (#1982) `go build -tags=gojson` to use `github.com/goccy/go-json` as default json handler Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1982 Reviewed-by: Lunny Xiao Co-authored-by: raizen666 Co-committed-by: raizen666 --- go.mod | 1 + go.sum | 2 ++ internal/json/gojson.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 internal/json/gojson.go diff --git a/go.mod b/go.mod index 78d8d7d4..dbc59e76 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.7.0 github.com/mattn/go-sqlite3 v1.14.6 + github.com/goccy/go-json v0.7.4 github.com/shopspring/decimal v1.2.0 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 diff --git a/go.sum b/go.sum index 85953202..da88d67a 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= +github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= diff --git a/internal/json/gojson.go b/internal/json/gojson.go new file mode 100644 index 00000000..4f1448e7 --- /dev/null +++ b/internal/json/gojson.go @@ -0,0 +1,28 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build gojson + +package json + +import ( + gojson "github.com/goccy/go-json" +) + +func init() { + DefaultJSONHandler = GOjson{} +} + +// GOjson implements JSONInterface via gojson +type GOjson struct{} + +// Marshal implements JSONInterface +func (GOjson) Marshal(v interface{}) ([]byte, error) { + return gojson.Marshal(v) +} + +// Unmarshal implements JSONInterface +func (GOjson) Unmarshal(data []byte, v interface{}) error { + return gojson.Unmarshal(data, v) +} From 86775af2ecd1de58d172ebcbd5a10f7412dc8689 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 19 Jul 2021 13:43:53 +0800 Subject: [PATCH 091/179] refactor and add setjson function (#1997) Fix #1992 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1997 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 4 ++ convert/time.go | 5 ++ dialects/dialect.go | 3 + dialects/mssql.go | 9 +++ dialects/mysql.go | 9 +++ dialects/oracle.go | 9 +++ dialects/postgres.go | 26 ++++++-- dialects/sqlite3.go | 11 +++- engine.go | 3 + scan.go | 3 + session.go | 154 ++++++++++++++----------------------------- session_convert.go | 70 -------------------- session_find.go | 6 ++ session_get.go | 5 +- session_iterate.go | 3 + session_query.go | 9 +++ session_update.go | 3 + 17 files changed, 148 insertions(+), 184 deletions(-) delete mode 100644 session_convert.go diff --git a/convert.go b/convert.go index 533dbe99..1aaf5dca 100644 --- a/convert.go +++ b/convert.go @@ -193,6 +193,8 @@ func asFloat64(src interface{}) (float64, error) { return float64(v.Int32), nil case *sql.NullInt64: return float64(v.Int64), nil + case *sql.NullFloat64: + return v.Float64, nil } rv := reflect.ValueOf(src) @@ -717,6 +719,8 @@ func convertAssignV(dv reflect.Value, src interface{}) error { func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { switch tp.Kind() { + case reflect.Ptr: + return asKind(vv.Elem(), tp.Elem()) case reflect.Int64: return vv.Int(), nil case reflect.Int: diff --git a/convert/time.go b/convert/time.go index 5a3e5246..283c7f83 100644 --- a/convert/time.go +++ b/convert/time.go @@ -8,11 +8,16 @@ import ( "fmt" "strconv" "time" + + "xorm.io/xorm/internal/utils" ) // String2Time converts a string to time with original location func String2Time(s string, originalLocation *time.Location, convertedLocation *time.Location) (*time.Time, error) { if len(s) == 19 { + if s == utils.ZeroTime0 || s == utils.ZeroTime1 { + return &time.Time{}, nil + } dt, err := time.ParseInLocation("2006-01-02 15:04:05", s, originalLocation) if err != nil { return nil, err diff --git a/dialects/dialect.go b/dialects/dialect.go index df33155d..81d1ee8d 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -118,6 +118,9 @@ func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query stri defer rows.Close() if rows.Next() { + if rows.Err() != nil { + return true, rows.Err() + } return true, nil } return false, nil diff --git a/dialects/mssql.go b/dialects/mssql.go index e708ba80..08232487 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -456,6 +456,9 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { + if rows.Err() != nil { + return nil, nil, rows.Err() + } var name, ctype, vdefault string var maxLen, precision, scale int var nullable, isPK, defaultIsNull, isIncrement bool @@ -524,6 +527,9 @@ func (db *mssql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema tables := make([]*schemas.Table, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) @@ -558,6 +564,9 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? indexes := make(map[string]*schemas.Index, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } var indexType int var indexName, colName, isUnique string diff --git a/dialects/mysql.go b/dialects/mysql.go index 9312c071..88c1038e 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -405,6 +405,9 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { + if rows.Err() != nil { + return nil, nil, rows.Err() + } col := new(schemas.Column) col.Indexes = make(map[string]int) @@ -519,6 +522,9 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema tables := make([]*schemas.Table, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } table := schemas.NewEmptyTable() var name, engine string var autoIncr, comment *string @@ -566,6 +572,9 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName indexes := make(map[string]*schemas.Index, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } var indexType int var indexName, colName, nonUnique string err = rows.Scan(&indexName, &nonUnique, &colName) diff --git a/dialects/oracle.go b/dialects/oracle.go index 5dd92887..9240046a 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -677,6 +677,9 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { + if rows.Err() != nil { + return nil, nil, rows.Err() + } col := new(schemas.Column) col.Indexes = make(map[string]int) @@ -772,6 +775,9 @@ func (db *oracle) GetTables(queryer core.Queryer, ctx context.Context) ([]*schem tables := make([]*schemas.Table, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } table := schemas.NewEmptyTable() err = rows.Scan(&table.Name) if err != nil { @@ -796,6 +802,9 @@ func (db *oracle) GetIndexes(queryer core.Queryer, ctx context.Context, tableNam indexes := make(map[string]*schemas.Index, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } var indexType int var indexName, colName, uniqueness string diff --git a/dialects/postgres.go b/dialects/postgres.go index 4ec780e8..e1dca631 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -810,7 +810,7 @@ func (db *postgres) Version(ctx context.Context, queryer core.Queryer) (*schemas var version string if !rows.Next() { - return nil, errors.New("Unknow version") + return nil, errors.New("unknow version") } if err := rows.Scan(&version); err != nil { @@ -1098,6 +1098,9 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A colSeq := make([]string, 0) for rows.Next() { + if rows.Err() != nil { + return nil, nil, rows.Err() + } col := new(schemas.Column) col.Indexes = make(map[string]int) @@ -1192,7 +1195,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A } } if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, fmt.Errorf("Unknown colType: %s - %s", dataType, col.SQLType.Name) + return nil, nil, fmt.Errorf("unknown colType: %s - %s", dataType, col.SQLType.Name) } col.Length = maxLen @@ -1200,13 +1203,13 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A if !col.DefaultIsEmpty { if col.SQLType.IsText() { if strings.HasSuffix(col.Default, "::character varying") { - col.Default = strings.TrimRight(col.Default, "::character varying") + col.Default = strings.TrimSuffix(col.Default, "::character varying") } else if !strings.HasPrefix(col.Default, "'") { col.Default = "'" + col.Default + "'" } } else if col.SQLType.IsTime() { if strings.HasSuffix(col.Default, "::timestamp without time zone") { - col.Default = strings.TrimRight(col.Default, "::timestamp without time zone") + col.Default = strings.TrimSuffix(col.Default, "::timestamp without time zone") } } } @@ -1234,6 +1237,9 @@ func (db *postgres) GetTables(queryer core.Queryer, ctx context.Context) ([]*sch tables := make([]*schemas.Table, 0) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) @@ -1259,7 +1265,7 @@ func getIndexColName(indexdef string) []string { func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} - s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1") + s := "SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1" if len(db.getSchema()) != 0 { args = append(args, db.getSchema()) s = s + " AND schemaname=$2" @@ -1271,8 +1277,11 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN } defer rows.Close() - indexes := make(map[string]*schemas.Index, 0) + indexes := make(map[string]*schemas.Index) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } var indexType int var indexName, indexdef string var colNames []string @@ -1450,6 +1459,9 @@ func QueryDefaultPostgresSchema(ctx context.Context, queryer core.Queryer) (stri } defer rows.Close() if rows.Next() { + if rows.Err() != nil { + return "", rows.Err() + } var defaultSchema string if err = rows.Scan(&defaultSchema); err != nil { return "", err @@ -1458,5 +1470,5 @@ func QueryDefaultPostgresSchema(ctx context.Context, queryer core.Queryer) (stri return strings.TrimSpace(parts[len(parts)-1]), nil } - return "", errors.New("No default schema") + return "", errors.New("no default schema") } diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 581272ad..da28d9d1 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -415,12 +415,14 @@ func (db *sqlite3) GetColumns(queryer core.Queryer, ctx context.Context, tableNa defer rows.Close() var name string - for rows.Next() { + if rows.Next() { + if rows.Err() != nil { + return nil, nil, rows.Err() + } err = rows.Scan(&name) if err != nil { return nil, nil, err } - break } if name == "" { @@ -496,8 +498,11 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa } defer rows.Close() - indexes := make(map[string]*schemas.Index, 0) + indexes := make(map[string]*schemas.Index) for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } var tmpSQL sql.NullString err = rows.Scan(&tmpSQL) if err != nil { diff --git a/engine.go b/engine.go index b4ef9593..35104b04 100644 --- a/engine.go +++ b/engine.go @@ -551,6 +551,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch sess := engine.NewSession() defer sess.Close() for rows.Next() { + if rows.Err() != nil { + return rows.Err() + } _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") if err != nil { return err diff --git a/scan.go b/scan.go index e4c0e4a1..444aa8ac 100644 --- a/scan.go +++ b/scan.go @@ -286,6 +286,9 @@ func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { return nil, err } for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } result, err := row2mapBytes(rows, types, fields) if err != nil { return nil, err diff --git a/session.go b/session.go index 5557d717..8c1d8c3b 100644 --- a/session.go +++ b/session.go @@ -364,25 +364,24 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, return } -func (session *Session) getField(dataStruct *reflect.Value, key string, table *schemas.Table, idx int) (*reflect.Value, error) { - var col *schemas.Column - if col = table.GetColumnIdx(key, idx); col == nil { - return nil, ErrFieldIsNotExist{key, table.Name} +func (session *Session) getField(dataStruct *reflect.Value, table *schemas.Table, colName string, idx int) (*schemas.Column, *reflect.Value, error) { + var col = table.GetColumnIdx(colName, idx) + if col == nil { + return nil, nil, ErrFieldIsNotExist{colName, table.Name} } fieldValue, err := col.ValueOfV(dataStruct) if err != nil { - return nil, err + return nil, nil, err } if fieldValue == nil { - return nil, ErrFieldIsNotValid{key, table.Name} + return nil, nil, ErrFieldIsNotValid{colName, table.Name} } - if !fieldValue.IsValid() || !fieldValue.CanSet() { - return nil, ErrFieldIsNotValid{key, table.Name} + return nil, nil, ErrFieldIsNotValid{colName, table.Name} } - return fieldValue, nil + return col, fieldValue, nil } // Cell cell is a result of one column field @@ -392,6 +391,9 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sq table *schemas.Table, newElemFunc func([]string) reflect.Value, sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { for rows.Next() { + if rows.Err() != nil { + return rows.Err() + } var newValue = newElemFunc(fields) bean := newValue.Interface() dataStruct := newValue.Elem() @@ -435,6 +437,36 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, types []*sql return scanResults, nil } +func (session *Session) setJSON(fieldValue *reflect.Value, fieldType reflect.Type, scanResult interface{}) error { + bs, ok := asBytes(scanResult) + if !ok { + return fmt.Errorf("unsupported database data type: %#v", scanResult) + } + if len(bs) == 0 { + return nil + } + + if fieldType.Kind() == reflect.String { + fieldValue.SetString(string(bs)) + return nil + } + + if fieldValue.CanAddr() { + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + return err + } + } else { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + return nil +} + func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflect.Value, scanResult interface{}, table *schemas.Table) error { v, ok := scanResult.(*interface{}) @@ -445,12 +477,6 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec return nil } - rawValue := reflect.Indirect(reflect.ValueOf(scanResult)) - // if row is null then ignore - if rawValue.Interface() == nil { - return nil - } - if fieldValue.CanAddr() { if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { data, ok := asBytes(scanResult) @@ -477,40 +503,11 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec return structConvert.FromDB(data) } - rawValueType := reflect.TypeOf(rawValue.Interface()) - vv := reflect.ValueOf(rawValue.Interface()) + vv := reflect.ValueOf(scanResult) fieldType := fieldValue.Type() if col.IsJSON { - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(schemas.BytesType) { - bs = vv.Bytes() - } else { - return fmt.Errorf("unsupported database data type: %s %v", col.Name, rawValueType.Kind()) - } - - if len(bs) > 0 { - if fieldType.Kind() == reflect.String { - fieldValue.SetString(string(bs)) - return nil - } - if fieldValue.CanAddr() { - err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - return err - } - } else { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - } - return nil + return session.setJSON(fieldValue, fieldType, scanResult) } switch fieldType.Kind() { @@ -529,30 +526,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } return nil case reflect.Complex64, reflect.Complex128: - // TODO: reimplement this - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(schemas.BytesType) { - bs = vv.Bytes() - } - - if len(bs) > 0 { - if fieldValue.CanAddr() { - err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - return err - } - } else { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - } - return nil + return session.setJSON(fieldValue, fieldType, scanResult) case reflect.Slice, reflect.Array: bs, ok := asBytes(scanResult) if ok && fieldType.Elem().Kind() == reflect.Uint8 { @@ -602,33 +576,11 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec fieldValue.Set(reflect.ValueOf(*t).Convert(fieldType)) return nil } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { - err := nulVal.Scan(vv.Interface()) + err := nulVal.Scan(scanResult) if err == nil { return nil } session.engine.logger.Errorf("sql.Sanner error: %v", err) - } else if col.IsJSON { - if rawValueType.Kind() == reflect.String { - x := reflect.New(fieldType) - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - return nil - } else if rawValueType.Kind() == reflect.Slice { - x := reflect.New(fieldType) - if len(vv.Bytes()) > 0 { - err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - return err - } - fieldValue.Set(x.Elem()) - } - return nil - } } else if session.statement.UseCascade { table, err := session.engine.tagParser.ParseWithCache(*fieldValue) if err != nil { @@ -639,7 +591,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec return errors.New("unsupported non or composited primary key cascade") } var pk = make(schemas.PK, len(table.PrimaryKeys)) - pk[0], err = asKind(vv, rawValueType) + pk[0], err = asKind(vv, reflect.TypeOf(scanResult)) if err != nil { return err } @@ -675,9 +627,9 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b var tempMap = make(map[string]int) var pk schemas.PK - for ii, key := range fields { + for i, colName := range fields { var idx int - var lKey = strings.ToLower(key) + var lKey = strings.ToLower(colName) var ok bool if idx, ok = tempMap[lKey]; !ok { @@ -685,13 +637,9 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } else { idx = idx + 1 } - tempMap[lKey] = idx - col := table.GetColumnIdx(key, idx) - var scanResult = scanResults[ii] - - fieldValue, err := session.getField(dataStruct, key, table, idx) + col, fieldValue, err := session.getField(dataStruct, table, colName, idx) if err != nil { if _, ok := err.(ErrFieldIsNotValid); !ok { session.engine.logger.Warnf("%v", err) @@ -702,11 +650,11 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b continue } - if err := session.convertBeanField(col, fieldValue, scanResult, table); err != nil { + if err := session.convertBeanField(col, fieldValue, scanResults[i], table); err != nil { return nil, err } if col.IsPrimaryKey { - pk = append(pk, scanResult) + pk = append(pk, scanResults[i]) } } return pk, nil diff --git a/session_convert.go b/session_convert.go deleted file mode 100644 index 452801e2..00000000 --- a/session_convert.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 The Xorm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xorm - -import ( - "fmt" - "strconv" - "strings" - "time" - - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" -) - -func (session *Session) str2Time(col *schemas.Column, data string) (outTime time.Time, outErr error) { - sdata := strings.TrimSpace(data) - var x time.Time - var err error - - var parseLoc = session.engine.DatabaseTZ - if col.TimeZone != nil { - parseLoc = col.TimeZone - } - - if sdata == utils.ZeroTime0 || sdata == utils.ZeroTime1 { - } else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column - // time stamp - sd, err := strconv.ParseInt(sdata, 10, 64) - if err == nil { - x = time.Unix(sd, 0) - } - } else if len(sdata) > 19 && strings.Contains(sdata, "-") { - x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc) - session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.Name, x, sdata) - if err != nil { - x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc) - } - if err != nil { - x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc) - } - } else if len(sdata) == 19 && strings.Contains(sdata, "-") { - x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc) - } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { - x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) - } else if col.SQLType.Name == schemas.Time { - if strings.Contains(sdata, " ") { - ssd := strings.Split(sdata, " ") - sdata = ssd[1] - } - - sdata = strings.TrimSpace(sdata) - if session.engine.dialect.URI().DBType == schemas.MYSQL && len(sdata) > 8 { - sdata = sdata[len(sdata)-8:] - } - - st := fmt.Sprintf("2006-01-02 %v", sdata) - x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc) - } else { - outErr = fmt.Errorf("unsupported time format %v", sdata) - return - } - if err != nil { - outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err) - return - } - outTime = x.In(session.engine.TZLocation) - return -} diff --git a/session_find.go b/session_find.go index 41d68479..89e34e80 100644 --- a/session_find.go +++ b/session_find.go @@ -255,6 +255,9 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } for rows.Next() { + if rows.Err() != nil { + return rows.Err() + } var newValue = newElemFunc(fields) bean := newValue.Interface() @@ -322,6 +325,9 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in var i int ids = make([]schemas.PK, 0) for rows.Next() { + if rows.Err() != nil { + return rows.Err() + } i++ if i > 500 { session.engine.logger.Debugf("[cacheFind] ids length > 500, no cache") diff --git a/session_get.go b/session_get.go index fa97e68e..1062bd9d 100644 --- a/session_get.go +++ b/session_get.go @@ -313,9 +313,12 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf defer rows.Close() if rows.Next() { + if rows.Err() != nil { + return true, rows.Err() + } err = rows.ScanSlice(&res) if err != nil { - return false, err + return true, err } } else { return false, ErrCacheFailed diff --git a/session_iterate.go b/session_iterate.go index 8cab8f48..dbbeb3f4 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -43,6 +43,9 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { i := 0 for rows.Next() { + if rows.Err() != nil { + return rows.Err() + } b := reflect.New(rows.beanType).Interface() err = rows.Scan(b) if err != nil { diff --git a/session_query.go b/session_query.go index d14c3908..8543ba12 100644 --- a/session_query.go +++ b/session_query.go @@ -33,6 +33,9 @@ func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string } for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } result, err := session.engine.row2mapStr(rows, types, fields) if err != nil { return nil, err @@ -54,6 +57,9 @@ func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]stri } for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } record, err := session.engine.row2sliceStr(rows, types, fields) if err != nil { return nil, err @@ -114,6 +120,9 @@ func (session *Session) rows2Interfaces(rows *core.Rows) (resultsSlice []map[str return nil, err } for rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } result, err := session.engine.row2mapInterface(rows, types, fields) if err != nil { return nil, err diff --git a/session_update.go b/session_update.go index 78907e43..32e28ae0 100644 --- a/session_update.go +++ b/session_update.go @@ -59,6 +59,9 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri ids = make([]schemas.PK, 0) for rows.Next() { + if rows.Err() != nil { + return rows.Err() + } var res = make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { From a7e010df2dd0e38ac86a587fc760858423a8f480 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 20 Jul 2021 13:46:24 +0800 Subject: [PATCH 092/179] refactor insert condition generation (#1998) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1998 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 4 +- convert/interface.go | 1 + convert/time.go | 2 +- dialects/dialect.go | 5 +- dialects/driver.go | 11 -- dialects/mssql.go | 26 +-- dialects/mysql.go | 38 ++-- dialects/oracle.go | 35 ++-- dialects/postgres.go | 38 ++-- dialects/postgres_test.go | 2 - dialects/sqlite3.go | 26 +-- engine.go | 6 +- go.mod | 17 +- go.sum | 35 +++- integrations/engine_test.go | 1 - integrations/session_get_test.go | 10 +- integrations/session_insert_test.go | 2 +- integrations/session_update_test.go | 1 - internal/statements/statement.go | 269 ++++++++++++++-------------- internal/statements/values.go | 2 +- internal/utils/strings.go | 4 +- names/mapper.go | 2 +- rows.go | 35 +--- scan.go | 14 +- schemas/table_test.go | 2 - schemas/type.go | 1 + session.go | 5 +- session_exist.go | 5 +- session_find.go | 11 +- session_get.go | 11 +- session_insert.go | 1 - session_iterate.go | 5 +- session_query.go | 18 +- session_update.go | 6 +- tags/parser.go | 1 + 35 files changed, 324 insertions(+), 328 deletions(-) diff --git a/convert.go b/convert.go index 1aaf5dca..c3eb4de9 100644 --- a/convert.go +++ b/convert.go @@ -373,7 +373,6 @@ func asTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time. case *sql.NullInt64: tm := time.Unix(t.Int64, 0).In(uiLoc) return &tm, nil - } return nil, fmt.Errorf("unsupported value %#v as time", src) } @@ -751,7 +750,6 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { } return v, nil } - } return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) } @@ -946,8 +944,10 @@ var ( _ sql.Scanner = &EmptyScanner{} ) +// EmptyScanner represents an empty scanner which will ignore the scan type EmptyScanner struct{} +// Scan implements sql.Scanner func (EmptyScanner) Scan(value interface{}) error { return nil } diff --git a/convert/interface.go b/convert/interface.go index 2b055253..b0f28c81 100644 --- a/convert/interface.go +++ b/convert/interface.go @@ -10,6 +10,7 @@ import ( "time" ) +// Interface2Interface converts interface of pointer as interface of value func Interface2Interface(userLocation *time.Location, v interface{}) (interface{}, error) { if v == nil { return nil, nil diff --git a/convert/time.go b/convert/time.go index 283c7f83..6a53171b 100644 --- a/convert/time.go +++ b/convert/time.go @@ -45,5 +45,5 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t return &tm, nil } } - return nil, fmt.Errorf("unsupported convertion from %s to time", s) + return nil, fmt.Errorf("unsupported conversion from %s to time", s) } diff --git a/dialects/dialect.go b/dialects/dialect.go index 81d1ee8d..fc11eac1 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -118,12 +118,9 @@ func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query stri defer rows.Close() if rows.Next() { - if rows.Err() != nil { - return true, rows.Err() - } return true, nil } - return false, nil + return false, rows.Err() } // IsColumnExist returns true if the column of the table exist diff --git a/dialects/driver.go b/dialects/driver.go index 0b6187d3..c511b665 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -18,14 +18,9 @@ type ScanContext struct { UserLocation *time.Location } -type DriverFeatures struct { - SupportNullable bool -} - // Driver represents a database driver type Driver interface { Parse(string, string) (*URI, error) - Features() DriverFeatures GenScanResult(string) (interface{}, error) // according given column type generating a suitable scan interface Scan(*ScanContext, *core.Rows, []*sql.ColumnType, ...interface{}) error } @@ -82,9 +77,3 @@ type baseDriver struct{} func (b *baseDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, v ...interface{}) error { return rows.Scan(v...) } - -func (b *baseDriver) Features() DriverFeatures { - return DriverFeatures{ - SupportNullable: true, - } -} diff --git a/dialects/mssql.go b/dialects/mssql.go index 08232487..742928b0 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -264,6 +264,9 @@ func (db *mssql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve var version, level, edition string if !rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } return nil, errors.New("unknow version") } @@ -456,9 +459,6 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - if rows.Err() != nil { - return nil, nil, rows.Err() - } var name, ctype, vdefault string var maxLen, precision, scale int var nullable, isPK, defaultIsNull, isIncrement bool @@ -512,6 +512,9 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cols[col.Name] = col colSeq = append(colSeq, col.Name) } + if rows.Err() != nil { + return nil, nil, rows.Err() + } return colSeq, cols, nil } @@ -527,9 +530,6 @@ func (db *mssql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema tables := make([]*schemas.Table, 0) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) @@ -539,6 +539,9 @@ func (db *mssql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema table.Name = strings.Trim(name, "` ") tables = append(tables, table) } + if rows.Err() != nil { + return nil, rows.Err() + } return tables, nil } @@ -562,11 +565,8 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } defer rows.Close() - indexes := make(map[string]*schemas.Index, 0) + indexes := make(map[string]*schemas.Index) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } var indexType int var indexName, colName, isUnique string @@ -604,6 +604,9 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } index.AddColumn(colName) } + if rows.Err() != nil { + return nil, rows.Err() + } return indexes, nil } @@ -664,8 +667,7 @@ func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) { for _, c := range kv { vv := strings.Split(strings.TrimSpace(c), "=") if len(vv) == 2 { - switch strings.ToLower(vv[0]) { - case "database": + if strings.ToLower(vv[0]) == "database" { dbName = vv[1] } } diff --git a/dialects/mysql.go b/dialects/mysql.go index 88c1038e..71ee3864 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -213,7 +213,10 @@ func (db *mysql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve var version string if !rows.Next() { - return nil, errors.New("Unknow version") + if rows.Err() != nil { + return nil, rows.Err() + } + return nil, errors.New("unknow version") } if err := rows.Scan(&version); err != nil { @@ -254,9 +257,6 @@ func (db *mysql) SetParams(params map[string]string) { fallthrough case "COMPRESSED": db.rowFormat = t - break - default: - break } } } @@ -405,9 +405,6 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - if rows.Err() != nil { - return nil, nil, rows.Err() - } col := new(schemas.Column) col.Indexes = make(map[string]int) @@ -506,6 +503,9 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName cols[col.Name] = col colSeq = append(colSeq, col.Name) } + if rows.Err() != nil { + return nil, nil, rows.Err() + } return colSeq, cols, nil } @@ -522,9 +522,6 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema tables := make([]*schemas.Table, 0) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } table := schemas.NewEmptyTable() var name, engine string var autoIncr, comment *string @@ -540,6 +537,9 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema table.StoreEngine = engine tables = append(tables, table) } + if rows.Err() != nil { + return nil, rows.Err() + } return tables, nil } @@ -570,11 +570,8 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName } defer rows.Close() - indexes := make(map[string]*schemas.Index, 0) + indexes := make(map[string]*schemas.Index) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } var indexType int var indexName, colName, nonUnique string err = rows.Scan(&indexName, &nonUnique, &colName) @@ -586,7 +583,7 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName continue } - if "YES" == nonUnique || nonUnique == "1" { + if nonUnique == "YES" || nonUnique == "1" { indexType = schemas.IndexType } else { indexType = schemas.UniqueType @@ -610,6 +607,9 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName } index.AddColumn(colName) } + if rows.Err() != nil { + return nil, rows.Err() + } return indexes, nil } @@ -696,14 +696,12 @@ func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { for _, kv := range kvs { splits := strings.Split(kv, "=") if len(splits) == 2 { - switch splits[0] { - case "charset": + if splits[0] == "charset" { uri.Charset = splits[1] } } } } - } } return uri, nil @@ -720,13 +718,13 @@ func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) { case "TINYINT", "SMALLINT", "MEDIUMINT", "INT": var s sql.NullInt32 return &s, nil - case "FLOAT", "REAL", "DOUBLE PRECISION": + case "FLOAT", "REAL", "DOUBLE PRECISION", "DOUBLE": var s sql.NullFloat64 return &s, nil case "DECIMAL", "NUMERIC": var s sql.NullString return &s, nil - case "DATETIME": + case "DATETIME", "TIMESTAMP": var s sql.NullTime return &s, nil case "BIT": diff --git a/dialects/oracle.go b/dialects/oracle.go index 9240046a..902e0c66 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -525,6 +525,9 @@ func (db *oracle) Version(ctx context.Context, queryer core.Queryer) (*schemas.V var version string if !rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } return nil, errors.New("unknow version") } @@ -677,9 +680,6 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - if rows.Err() != nil { - return nil, nil, rows.Err() - } col := new(schemas.Column) col.Indexes = make(map[string]int) @@ -759,6 +759,9 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam cols[col.Name] = col colSeq = append(colSeq, col.Name) } + if rows.Err() != nil { + return nil, nil, rows.Err() + } return colSeq, cols, nil } @@ -775,9 +778,6 @@ func (db *oracle) GetTables(queryer core.Queryer, ctx context.Context) ([]*schem tables := make([]*schemas.Table, 0) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } table := schemas.NewEmptyTable() err = rows.Scan(&table.Name) if err != nil { @@ -786,6 +786,9 @@ func (db *oracle) GetTables(queryer core.Queryer, ctx context.Context) ([]*schem tables = append(tables, table) } + if rows.Err() != nil { + return nil, rows.Err() + } return tables, nil } @@ -800,11 +803,8 @@ func (db *oracle) GetIndexes(queryer core.Queryer, ctx context.Context, tableNam } defer rows.Close() - indexes := make(map[string]*schemas.Index, 0) + indexes := make(map[string]*schemas.Index) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } var indexType int var indexName, colName, uniqueness string @@ -838,6 +838,9 @@ func (db *oracle) GetIndexes(queryer core.Queryer, ctx context.Context, tableNam } index.AddColumn(colName) } + if rows.Err() != nil { + return nil, rows.Err() + } return indexes, nil } @@ -851,7 +854,7 @@ type godrorDriver struct { baseDriver } -func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) { +func (g *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) { db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] @@ -863,8 +866,7 @@ func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) names := dsnPattern.SubexpNames() for i, match := range matches { - switch names[i] { - case "dbname": + if names[i] == "dbname" { db.DBName = match } } @@ -874,7 +876,7 @@ func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) return db, nil } -func (p *godrorDriver) GenScanResult(colType string) (interface{}, error) { +func (g *godrorDriver) GenScanResult(colType string) (interface{}, error) { switch colType { case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": var s sql.NullString @@ -900,7 +902,7 @@ type oci8Driver struct { // dataSourceName=user/password@ipv4:port/dbname // dataSourceName=user/password@[ipv6]:port/dbname -func (p *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { +func (o *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?P.*)\/(?P.*)@` + // user:password@ @@ -909,8 +911,7 @@ func (p *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { matches := dsnPattern.FindStringSubmatch(dataSourceName) names := dsnPattern.SubexpNames() for i, match := range matches { - switch names[i] { - case "dbname": + if names[i] == "dbname" { db.DBName = match } } diff --git a/dialects/postgres.go b/dialects/postgres.go index e1dca631..6462982d 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -810,6 +810,9 @@ func (db *postgres) Version(ctx context.Context, queryer core.Queryer) (*schemas var version string if !rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } return nil, errors.New("unknow version") } @@ -1062,7 +1065,10 @@ func (db *postgres) IsColumnExist(queryer core.Queryer, ctx context.Context, tab } defer rows.Close() - return rows.Next(), nil + if rows.Next() { + return true, nil + } + return false, rows.Err() } func (db *postgres) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { @@ -1098,9 +1104,6 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A colSeq := make([]string, 0) for rows.Next() { - if rows.Err() != nil { - return nil, nil, rows.Err() - } col := new(schemas.Column) col.Indexes = make(map[string]int) @@ -1216,6 +1219,9 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A cols[col.Name] = col colSeq = append(colSeq, col.Name) } + if rows.Err() != nil { + return nil, nil, rows.Err() + } return colSeq, cols, nil } @@ -1237,9 +1243,6 @@ func (db *postgres) GetTables(queryer core.Queryer, ctx context.Context) ([]*sch tables := make([]*schemas.Table, 0) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) @@ -1249,6 +1252,9 @@ func (db *postgres) GetTables(queryer core.Queryer, ctx context.Context) ([]*sch table.Name = name tables = append(tables, table) } + if rows.Err() != nil { + return nil, rows.Err() + } return tables, nil } @@ -1279,9 +1285,6 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN indexes := make(map[string]*schemas.Index) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } var indexType int var indexName, indexdef string var colNames []string @@ -1322,6 +1325,9 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN index.IsRegular = isRegular indexes[index.Name] = index } + if rows.Err() != nil { + return nil, rows.Err() + } return indexes, nil } @@ -1333,12 +1339,6 @@ type pqDriver struct { baseDriver } -func (b *pqDriver) Features() DriverFeatures { - return DriverFeatures{ - SupportNullable: false, - } -} - type values map[string]string func (vs values) Set(k, v string) { @@ -1459,9 +1459,6 @@ func QueryDefaultPostgresSchema(ctx context.Context, queryer core.Queryer) (stri } defer rows.Close() if rows.Next() { - if rows.Err() != nil { - return "", rows.Err() - } var defaultSchema string if err = rows.Scan(&defaultSchema); err != nil { return "", err @@ -1469,6 +1466,9 @@ func QueryDefaultPostgresSchema(ctx context.Context, queryer core.Queryer) (stri parts := strings.Split(defaultSchema, ",") return strings.TrimSpace(parts[len(parts)-1]), nil } + if rows.Err() != nil { + return "", rows.Err() + } return "", errors.New("no default schema") } diff --git a/dialects/postgres_test.go b/dialects/postgres_test.go index c0a8eb6f..e0c36f92 100644 --- a/dialects/postgres_test.go +++ b/dialects/postgres_test.go @@ -76,9 +76,7 @@ func TestParsePgx(t *testing.T) { } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } - } - } func TestGetIndexColName(t *testing.T) { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index da28d9d1..89f86147 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -169,7 +169,10 @@ func (db *sqlite3) Version(ctx context.Context, queryer core.Queryer) (*schemas. var version string if !rows.Next() { - return nil, errors.New("Unknow version") + if rows.Err() != nil { + return nil, rows.Err() + } + return nil, errors.New("unknow version") } if err := rows.Scan(&version); err != nil { @@ -416,14 +419,14 @@ func (db *sqlite3) GetColumns(queryer core.Queryer, ctx context.Context, tableNa var name string if rows.Next() { - if rows.Err() != nil { - return nil, nil, rows.Err() - } err = rows.Scan(&name) if err != nil { return nil, nil, err } } + if rows.Err() != nil { + return nil, nil, rows.Err() + } if name == "" { return nil, nil, errors.New("no table named " + tableName) @@ -485,6 +488,9 @@ func (db *sqlite3) GetTables(queryer core.Queryer, ctx context.Context) ([]*sche } tables = append(tables, table) } + if rows.Err() != nil { + return nil, rows.Err() + } return tables, nil } @@ -500,9 +506,6 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa indexes := make(map[string]*schemas.Index) for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } var tmpSQL sql.NullString err = rows.Scan(&tmpSQL) if err != nil { @@ -547,6 +550,9 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa index.IsRegular = isRegular indexes[index.Name] = index } + if rows.Err() != nil { + return nil, rows.Err() + } return indexes, nil } @@ -592,9 +598,3 @@ func (p *sqlite3Driver) GenScanResult(colType string) (interface{}, error) { return &r, nil } } - -func (b *sqlite3Driver) Features() DriverFeatures { - return DriverFeatures{ - SupportNullable: false, - } -} diff --git a/engine.go b/engine.go index 35104b04..20c07e13 100644 --- a/engine.go +++ b/engine.go @@ -551,9 +551,6 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch sess := engine.NewSession() defer sess.Close() for rows.Next() { - if rows.Err() != nil { - return rows.Err() - } _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") if err != nil { return err @@ -610,6 +607,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } + if rows.Err() != nil { + return rows.Err() + } // FIXME: Hack for postgres if dstDialect.URI().DBType == schemas.POSTGRES && table.AutoIncrColumn() != nil { diff --git a/go.mod b/go.mod index dbc59e76..1b3baf0c 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,17 @@ module xorm.io/xorm go 1.13 require ( - github.com/denisenkom/go-mssqldb v0.9.0 - github.com/go-sql-driver/mysql v1.5.0 - github.com/json-iterator/go v1.1.11 - github.com/lib/pq v1.7.0 - github.com/mattn/go-sqlite3 v1.14.6 + github.com/denisenkom/go-mssqldb v0.10.0 + github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.7.4 + github.com/json-iterator/go v1.1.11 + github.com/lib/pq v1.10.2 + github.com/mattn/go-sqlite3 v1.14.8 github.com/shopspring/decimal v1.2.0 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 - xorm.io/builder v0.3.8 + gopkg.in/yaml.v2 v2.2.2 // indirect + modernc.org/sqlite v1.11.2 + xorm.io/builder v0.3.9 ) diff --git a/go.sum b/go.sum index da88d67a..3d4b72a6 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= +github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -28,12 +34,14 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= -github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= @@ -49,10 +57,13 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6O github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -103,28 +114,46 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0= modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= +modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= +modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA= modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY= +modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= +modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8= modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= +modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= +modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY= modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU= +modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w= +modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A= modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs= modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc= +modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= @@ -132,3 +161,5 @@ modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE= xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= +xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/integrations/engine_test.go b/integrations/engine_test.go index a594ee46..b5ecb2c2 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -172,7 +172,6 @@ func TestDumpTables(t *testing.T) { name := fmt.Sprintf("dump_%v-table.sql", tp) t.Run(name, func(t *testing.T) { assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, name, tp)) - }) } diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index b1dffe14..d3ce2a11 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -818,8 +818,9 @@ func TestGetBigFloat(t *testing.T) { } type GetBigFloat2 struct { - Id int64 - Money *big.Float `xorm:"decimal(22,2)"` + Id int64 + Money *big.Float `xorm:"decimal(22,2)"` + Money2 big.Float `xorm:"decimal(22,2)"` } assert.NoError(t, PrepareEngine()) @@ -827,7 +828,8 @@ func TestGetBigFloat(t *testing.T) { { var gf2 = GetBigFloat2{ - Money: big.NewFloat(9999999.99), + Money: big.NewFloat(9999999.99), + Money2: *big.NewFloat(99.99), } _, err := testEngine.Insert(&gf2) assert.NoError(t, err) @@ -845,12 +847,14 @@ func TestGetBigFloat(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.True(t, gf3.Money.String() == gf2.Money.String(), "%v != %v", gf3.Money.String(), gf2.Money.String()) + assert.True(t, gf3.Money2.String() == gf2.Money2.String(), "%v != %v", gf3.Money2.String(), gf2.Money2.String()) var gfs []GetBigFloat2 err = testEngine.Find(&gfs) assert.NoError(t, err) assert.EqualValues(t, 1, len(gfs)) assert.True(t, gfs[0].Money.String() == gf2.Money.String(), "%v != %v", gfs[0].Money.String(), gf2.Money.String()) + assert.True(t, gfs[0].Money2.String() == gf2.Money2.String(), "%v != %v", gfs[0].Money2.String(), gf2.Money2.String()) } } diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index a023ab72..ce52d3c4 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -202,7 +202,7 @@ func TestInsertDefault2(t *testing.T) { Id int64 Name string Url string `xorm:"text"` - CheckTime time.Time `xorm:"not null default '2000-01-01 00:00:00' TIMESTAMP"` + CheckTime time.Time `xorm:"not null default '2000-01-01 00:00:00'"` } di := new(DefaultInsert2) diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 22808d60..cc1042b6 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -1313,7 +1313,6 @@ func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { assert.EqualValues(t, true, has) assert.EqualValues(t, "", record.OnlyFromDBField) return &record - } assert.NoError(t, PrepareEngine()) assertSync(t, new(TestOnlyFromDBField)) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index bfe9987f..0e245a96 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -8,6 +8,7 @@ import ( "database/sql/driver" "errors" "fmt" + "math/big" "reflect" "strings" "time" @@ -662,10 +663,6 @@ func (statement *Statement) GenIndexSQL() []string { return sqls } -func uniqueName(tableName, uqeName string) string { - return fmt.Sprintf("UQE_%v_%v", tableName, uqeName) -} - // GenUniqueSQL generates unique SQL func (statement *Statement) GenUniqueSQL() []string { var sqls []string @@ -693,6 +690,138 @@ func (statement *Statement) GenDelIndexSQL() []string { return sqls } +func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect.Type, col *schemas.Column, allUseBool, requiredField bool) (interface{}, bool, error) { + switch fieldType.Kind() { + case reflect.Ptr: + if fieldValue.IsNil() { + return nil, true, nil + } + return statement.asDBCond(fieldValue.Elem(), fieldType.Elem(), col, allUseBool, requiredField) + case reflect.Bool: + if allUseBool || requiredField { + return fieldValue.Interface(), true, nil + } + // if a bool in a struct, it will not be as a condition because it default is false, + // please use Where() instead + return nil, false, nil + case reflect.String: + if !requiredField && fieldValue.String() == "" { + return nil, false, nil + } + // for MyString, should convert to string or panic + if fieldType.String() != reflect.String.String() { + return fieldValue.String(), true, nil + } + return fieldValue.Interface(), true, nil + case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64: + if !requiredField && fieldValue.Int() == 0 { + return nil, false, nil + } + return fieldValue.Interface(), true, nil + case reflect.Float32, reflect.Float64: + if !requiredField && fieldValue.Float() == 0.0 { + return nil, false, nil + } + return fieldValue.Interface(), true, nil + case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64: + if !requiredField && fieldValue.Uint() == 0 { + return nil, false, nil + } + return fieldValue.Interface(), true, nil + case reflect.Struct: + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) + if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { + return nil, false, nil + } + return dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t), true, nil + } else if fieldType.ConvertibleTo(schemas.BigFloatType) { + t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float) + v := t.String() + if v == "0" { + return nil, false, nil + } + return t.String(), true, nil + } else if _, ok := reflect.New(fieldType).Interface().(convert.Conversion); ok { + return nil, false, nil + } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { + val, _ := valNul.Value() + if val == nil && !requiredField { + return nil, false, nil + } + return val, true, nil + } else { + if col.IsJSON { + if col.SQLType.IsText() { + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, false, err + } + return string(bytes), true, nil + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, false, err + } + return bytes, true, nil + } + } else { + table, err := statement.tagParser.ParseWithCache(fieldValue) + if err != nil { + return fieldValue.Interface(), true, nil + } + + if len(table.PrimaryKeys) == 1 { + pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) + // fix non-int pk issues + //if pkField.Int() != 0 { + if pkField.IsValid() && !utils.IsZero(pkField.Interface()) { + return pkField.Interface(), true, nil + } + return nil, false, nil + } + return nil, false, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys) + } + } + case reflect.Array: + return nil, false, nil + case reflect.Slice, reflect.Map: + if fieldValue == reflect.Zero(fieldType) { + return nil, false, nil + } + if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 { + return nil, false, nil + } + + if col.SQLType.IsText() { + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, false, err + } + return string(bytes), true, nil + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) && + fieldType.Elem().Kind() == reflect.Uint8 { + if fieldValue.Len() > 0 { + return fieldValue.Bytes(), true, nil + } + return nil, false, nil + } + bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, false, err + } + return bytes, true, nil + } + return nil, false, nil + } + return fieldValue.Interface(), true, nil +} + func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool, @@ -747,9 +876,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, continue } - fieldType := reflect.TypeOf(fieldValue.Interface()) requiredField := useAllCols - if b, ok := getFlagForColumn(mustColumnMap, col); ok { if b { requiredField = true @@ -758,6 +885,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, } } + fieldType := reflect.TypeOf(fieldValue.Interface()) if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { @@ -774,131 +902,12 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, } } - var val interface{} - switch fieldType.Kind() { - case reflect.Bool: - if allUseBool || requiredField { - val = fieldValue.Interface() - } else { - // if a bool in a struct, it will not be as a condition because it default is false, - // please use Where() instead - continue - } - case reflect.String: - if !requiredField && fieldValue.String() == "" { - continue - } - // for MyString, should convert to string or panic - if fieldType.String() != reflect.String.String() { - val = fieldValue.String() - } else { - val = fieldValue.Interface() - } - case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64: - if !requiredField && fieldValue.Int() == 0 { - continue - } - val = fieldValue.Interface() - case reflect.Float32, reflect.Float64: - if !requiredField && fieldValue.Float() == 0.0 { - continue - } - val = fieldValue.Interface() - case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64: - if !requiredField && fieldValue.Uint() == 0 { - continue - } - val = fieldValue.Interface() - case reflect.Struct: - if fieldType.ConvertibleTo(schemas.TimeType) { - t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) - if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { - continue - } - val = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) - } else if _, ok := reflect.New(fieldType).Interface().(convert.Conversion); ok { - continue - } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { - val, _ = valNul.Value() - if val == nil && !requiredField { - continue - } - } else { - if col.IsJSON { - if col.SQLType.IsText() { - bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return nil, err - } - val = string(bytes) - } else if col.SQLType.IsBlob() { - var bytes []byte - var err error - bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return nil, err - } - val = bytes - } - } else { - table, err := statement.tagParser.ParseWithCache(fieldValue) - if err != nil { - val = fieldValue.Interface() - } else { - if len(table.PrimaryKeys) == 1 { - pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) - // fix non-int pk issues - //if pkField.Int() != 0 { - if pkField.IsValid() && !utils.IsZero(pkField.Interface()) { - val = pkField.Interface() - } else { - continue - } - } else { - //TODO: how to handler? - return nil, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys) - } - } - } - } - case reflect.Array: + val, ok, err := statement.asDBCond(fieldValue, fieldType, col, allUseBool, requiredField) + if err != nil { + return nil, err + } + if !ok { continue - case reflect.Slice, reflect.Map: - if fieldValue == reflect.Zero(fieldType) { - continue - } - if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 { - continue - } - - if col.SQLType.IsText() { - bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return nil, err - } - val = string(bytes) - } else if col.SQLType.IsBlob() { - var bytes []byte - var err error - if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) && - fieldType.Elem().Kind() == reflect.Uint8 { - if fieldValue.Len() > 0 { - val = fieldValue.Bytes() - } else { - continue - } - } else { - bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return nil, err - } - val = bytes - } - } else { - continue - } - default: - val = fieldValue.Interface() } conds = append(conds, builder.Eq{colName: val}) diff --git a/internal/statements/values.go b/internal/statements/values.go index ee3821e9..c572ead5 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -23,7 +23,7 @@ var ( bigFloatType = reflect.TypeOf(big.Float{}) ) -// Value2Interface convert a field value of a struct to interface for puting into database +// Value2Interface convert a field value of a struct to interface for putting into database func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue reflect.Value) (interface{}, error) { if fieldValue.CanAddr() { if fieldConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { diff --git a/internal/utils/strings.go b/internal/utils/strings.go index 86469c0f..159e2876 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -13,7 +13,7 @@ func IndexNoCase(s, sep string) int { return strings.Index(strings.ToLower(s), strings.ToLower(sep)) } -// SplitNoCase split a string by a seperator with no care of capitalize +// SplitNoCase split a string by a separator with no care of capitalize func SplitNoCase(s, sep string) []string { idx := IndexNoCase(s, sep) if idx < 0 { @@ -22,7 +22,7 @@ func SplitNoCase(s, sep string) []string { return strings.Split(s, s[idx:idx+len(sep)]) } -// SplitNNoCase split n by a seperator with no care of capitalize +// SplitNNoCase split n by a separator with no care of capitalize func SplitNNoCase(s, sep string, n int) []string { idx := IndexNoCase(s, sep) if idx < 0 { diff --git a/names/mapper.go b/names/mapper.go index b0ce8076..69f67171 100644 --- a/names/mapper.go +++ b/names/mapper.go @@ -79,7 +79,7 @@ func (m SameMapper) Table2Obj(t string) string { return t } -// SnakeMapper implements IMapper and provides name transaltion between +// SnakeMapper implements IMapper and provides name translation between // struct and database table type SnakeMapper struct { } diff --git a/rows.go b/rows.go index 5e0a1ffe..8e7cc075 100644 --- a/rows.go +++ b/rows.go @@ -5,7 +5,6 @@ package xorm import ( - "database/sql" "errors" "fmt" "reflect" @@ -17,10 +16,9 @@ import ( // Rows rows wrapper a rows to type Rows struct { - session *Session - rows *core.Rows - beanType reflect.Type - lastError error + session *Session + rows *core.Rows + beanType reflect.Type } func newRows(session *Session, bean interface{}) (*Rows, error) { @@ -62,15 +60,6 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { // !oinume! Add " IS NULL" to WHERE whatever condiBean is given. // See https://gitea.com/xorm/xorm/issues/179 if col := table.DeletedColumn(); col != nil && !session.statement.GetUnscoped() { // tag "deleted" is enabled - var colName = session.engine.Quote(col.Name) - if addedTableName { - var nm = session.statement.TableName() - if len(session.statement.TableAlias) > 0 { - nm = session.statement.TableAlias - } - colName = session.engine.Quote(nm) + "." + colName - } - autoCond = session.statement.CondDeleted(col) } } @@ -86,7 +75,6 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { rows.rows, err = rows.session.queryRows(sqlStr, args...) if err != nil { - rows.lastError = err rows.Close() return nil, err } @@ -96,25 +84,18 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { // Next move cursor to next record, return false if end has reached func (rows *Rows) Next() bool { - if rows.lastError == nil && rows.rows != nil { - hasNext := rows.rows.Next() - if !hasNext { - rows.lastError = sql.ErrNoRows - } - return hasNext - } - return false + return rows.rows.Next() } // Err returns the error, if any, that was encountered during iteration. Err may be called after an explicit or implicit Close. func (rows *Rows) Err() error { - return rows.lastError + return rows.rows.Err() } // Scan row record to bean properties func (rows *Rows) Scan(bean interface{}) error { - if rows.lastError != nil { - return rows.lastError + if rows.Err() != nil { + return rows.Err() } if reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType { @@ -158,5 +139,5 @@ func (rows *Rows) Close() error { return rows.rows.Close() } - return rows.lastError + return rows.Err() } diff --git a/scan.go b/scan.go index 444aa8ac..ccd6938d 100644 --- a/scan.go +++ b/scan.go @@ -211,12 +211,8 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column scanResult = &sql.RawBytes{} replaced = true default: - var useNullable = true - if engine.driver.Features().SupportNullable { - nullable, ok := types[0].Nullable() - useNullable = ok && nullable - } - if useNullable { + nullable, ok := types[0].Nullable() + if !ok || nullable { scanResult, replaced, err = genScanResultsByBeanNullable(v) } else { scanResult, replaced, err = genScanResultsByBean(v) @@ -286,15 +282,15 @@ func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { return nil, err } for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } result, err := row2mapBytes(rows, types, fields) if err != nil { return nil, err } resultsSlice = append(resultsSlice, result) } + if rows.Err() != nil { + return nil, rows.Err() + } return resultsSlice, nil } diff --git a/schemas/table_test.go b/schemas/table_test.go index 0e35193f..f352675b 100644 --- a/schemas/table_test.go +++ b/schemas/table_test.go @@ -58,7 +58,6 @@ func TestGetColumnIdx(t *testing.T) { func BenchmarkGetColumnWithToLower(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range testsGetColumn { - if _, ok := table.columnsMap[strings.ToLower(test.name)]; !ok { b.Errorf("Column not found:%s", test.name) } @@ -69,7 +68,6 @@ func BenchmarkGetColumnWithToLower(b *testing.B) { func BenchmarkGetColumnIdxWithToLower(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range testsGetColumn { - if c, ok := table.columnsMap[strings.ToLower(test.name)]; ok { if test.idx < len(c) { continue diff --git a/schemas/type.go b/schemas/type.go index 62e66c2e..d64251bf 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -65,6 +65,7 @@ func (s *SQLType) IsTime() bool { return s.IsType(TIME_TYPE) } +// IsBool returns true if column is a boolean type func (s *SQLType) IsBool() bool { return s.IsType(BOOL_TYPE) } diff --git a/session.go b/session.go index 8c1d8c3b..62d6a770 100644 --- a/session.go +++ b/session.go @@ -391,9 +391,6 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sq table *schemas.Table, newElemFunc func([]string) reflect.Value, sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { for rows.Next() { - if rows.Err() != nil { - return rows.Err() - } var newValue = newElemFunc(fields) bean := newValue.Interface() dataStruct := newValue.Elem() @@ -415,7 +412,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sq bean: bean, }) } - return nil + return rows.Err() } func (session *Session) row2Slice(rows *core.Rows, fields []string, types []*sql.ColumnType, bean interface{}) ([]interface{}, error) { diff --git a/session_exist.go b/session_exist.go index e52c618e..b5e4a655 100644 --- a/session_exist.go +++ b/session_exist.go @@ -25,5 +25,8 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { } defer rows.Close() - return rows.Next(), nil + if rows.Next() { + return true, nil + } + return false, rows.Err() } diff --git a/session_find.go b/session_find.go index 89e34e80..010ecd6c 100644 --- a/session_find.go +++ b/session_find.go @@ -255,9 +255,6 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } for rows.Next() { - if rows.Err() != nil { - return rows.Err() - } var newValue = newElemFunc(fields) bean := newValue.Interface() @@ -278,7 +275,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return err } } - return nil + return rows.Err() } func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { @@ -325,9 +322,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in var i int ids = make([]schemas.PK, 0) for rows.Next() { - if rows.Err() != nil { - return rows.Err() - } i++ if i > 500 { session.engine.logger.Debugf("[cacheFind] ids length > 500, no cache") @@ -348,6 +342,9 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in ids = append(ids, pk) } + if rows.Err() != nil { + return rows.Err() + } session.engine.logger.Debugf("[cache] cache sql: %v, %v, %v, %v, %v", ids, tableName, sqlStr, newsql, args) err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) diff --git a/session_get.go b/session_get.go index 1062bd9d..08172524 100644 --- a/session_get.go +++ b/session_get.go @@ -159,10 +159,7 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, defer rows.Close() if !rows.Next() { - if rows.Err() != nil { - return false, rows.Err() - } - return false, nil + return false, rows.Err() } // WARN: Alougth rows return true, but we may also return error. @@ -313,14 +310,14 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf defer rows.Close() if rows.Next() { - if rows.Err() != nil { - return true, rows.Err() - } err = rows.ScanSlice(&res) if err != nil { return true, err } } else { + if rows.Err() != nil { + return false, rows.Err() + } return false, ErrCacheFailed } diff --git a/session_insert.go b/session_insert.go index b41dbbac..a9b8b7d2 100644 --- a/session_insert.go +++ b/session_insert.go @@ -325,7 +325,6 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { copy(afterClosures, session.afterClosures) session.afterInsertBeans[bean] = &afterClosures } - } else { if _, ok := interface{}(bean).(AfterInsertProcessor); ok { session.afterInsertBeans[bean] = nil diff --git a/session_iterate.go b/session_iterate.go index dbbeb3f4..f6301009 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -43,9 +43,6 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { i := 0 for rows.Next() { - if rows.Err() != nil { - return rows.Err() - } b := reflect.New(rows.beanType).Interface() err = rows.Scan(b) if err != nil { @@ -57,7 +54,7 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { } i++ } - return err + return rows.Err() } // BufferSize sets the buffersize for iterate diff --git a/session_query.go b/session_query.go index 8543ba12..a4070985 100644 --- a/session_query.go +++ b/session_query.go @@ -33,15 +33,15 @@ func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string } for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } result, err := session.engine.row2mapStr(rows, types, fields) if err != nil { return nil, err } resultsSlice = append(resultsSlice, result) } + if rows.Err() != nil { + return nil, rows.Err() + } return resultsSlice, nil } @@ -57,15 +57,15 @@ func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]stri } for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } record, err := session.engine.row2sliceStr(rows, types, fields) if err != nil { return nil, err } resultsSlice = append(resultsSlice, record) } + if rows.Err() != nil { + return nil, rows.Err() + } return resultsSlice, nil } @@ -120,15 +120,15 @@ func (session *Session) rows2Interfaces(rows *core.Rows) (resultsSlice []map[str return nil, err } for rows.Next() { - if rows.Err() != nil { - return nil, rows.Err() - } result, err := session.engine.row2mapInterface(rows, types, fields) if err != nil { return nil, err } resultsSlice = append(resultsSlice, result) } + if rows.Err() != nil { + return nil, rows.Err() + } return resultsSlice, nil } diff --git a/session_update.go b/session_update.go index 32e28ae0..4f8e6961 100644 --- a/session_update.go +++ b/session_update.go @@ -59,9 +59,6 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri ids = make([]schemas.PK, 0) for rows.Next() { - if rows.Err() != nil { - return rows.Err() - } var res = make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { @@ -84,6 +81,9 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri ids = append(ids, pk) } + if rows.Err() != nil { + return rows.Err() + } session.engine.logger.Debugf("[cache] find updated id: %v", ids) } /*else { session.engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args) diff --git a/tags/parser.go b/tags/parser.go index b793a8f1..72baa153 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -124,6 +124,7 @@ func addIndex(indexName string, table *schemas.Table, col *schemas.Column, index } } +// ErrIgnoreField represents an error to ignore field var ErrIgnoreField = errors.New("field will be ignored") func (parser *Parser) parseFieldWithNoTag(fieldIndex int, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { From e3239710112e47c0f9a06cabd500da234efc8ba1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Jul 2021 00:12:20 +0800 Subject: [PATCH 093/179] refactor some code (#2000) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2000 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/driver.go | 6 +++++ dialects/mssql.go | 6 +++++ dialects/mysql.go | 6 +++++ dialects/oracle.go | 6 +++++ dialects/postgres.go | 6 +++++ dialects/sqlite3.go | 6 +++++ session_insert.go | 64 ++++++++++---------------------------------- tags/parser.go | 11 ++++---- tags/tag.go | 12 ++++++--- 9 files changed, 64 insertions(+), 59 deletions(-) diff --git a/dialects/driver.go b/dialects/driver.go index c511b665..c63dbfa3 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -18,9 +18,15 @@ type ScanContext struct { UserLocation *time.Location } +// DriverFeatures represents driver feature +type DriverFeatures struct { + SupportReturnInsertedID bool +} + // Driver represents a database driver type Driver interface { Parse(string, string) (*URI, error) + Features() *DriverFeatures GenScanResult(string) (interface{}, error) // according given column type generating a suitable scan interface Scan(*ScanContext, *core.Rows, []*sql.ColumnType, ...interface{}) error } diff --git a/dialects/mssql.go b/dialects/mssql.go index 742928b0..7deade80 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -653,6 +653,12 @@ type odbcDriver struct { baseDriver } +func (p *odbcDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: false, + } +} + func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) { var dbName string diff --git a/dialects/mysql.go b/dialects/mysql.go index 71ee3864..0ad68833 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -674,6 +674,12 @@ type mysqlDriver struct { baseDriver } +func (p *mysqlDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: true, + } +} + func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] diff --git a/dialects/oracle.go b/dialects/oracle.go index 902e0c66..11a6653b 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -854,6 +854,12 @@ type godrorDriver struct { baseDriver } +func (g *godrorDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: false, + } +} + func (g *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) { db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( diff --git a/dialects/postgres.go b/dialects/postgres.go index 6462982d..8a0dd7a8 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1387,6 +1387,12 @@ func parseOpts(name string, o values) error { return nil } +func (p *pqDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: false, + } +} + func (p *pqDriver) Parse(driverName, dataSourceName string) (*URI, error) { db := &URI{DBType: schemas.POSTGRES} var err error diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 89f86147..dae6bf93 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -565,6 +565,12 @@ type sqlite3Driver struct { baseDriver } +func (p *sqlite3Driver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: true, + } +} + func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) { if strings.Contains(dataSourceName, "?") { dataSourceName = dataSourceName[:strings.Index(dataSourceName, "?")] diff --git a/session_insert.go b/session_insert.go index a9b8b7d2..f35cca53 100644 --- a/session_insert.go +++ b/session_insert.go @@ -9,7 +9,6 @@ import ( "fmt" "reflect" "sort" - "strconv" "strings" "time" @@ -334,13 +333,18 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { cleanupProcessorsClosures(&session.afterClosures) // cleanup after used } - // for postgres, many of them didn't implement lastInsertId, so we should - // implemented it ourself. - if session.engine.dialect.URI().DBType == schemas.ORACLE && len(table.AutoIncrement) > 0 { - res, err := session.queryBytes("select seq_atable.currval from dual", args...) + // if there is auto increment column and driver don't support return it + if len(table.AutoIncrement) > 0 && !session.engine.driver.Features().SupportReturnInsertedID { + var sql = sqlStr + if session.engine.dialect.URI().DBType == schemas.ORACLE { + sql = "select seq_atable.currval from dual" + } + + rows, err := session.queryRows(sql, args...) if err != nil { return 0, err } + defer rows.Close() defer handleAfterInsertProcessorFunc(bean) @@ -355,56 +359,16 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } } - if len(res) < 1 { - return 0, errors.New("insert no error but not returned id") - } - - idByte := res[0][table.AutoIncrement] - id, err := strconv.ParseInt(string(idByte), 10, 64) - if err != nil || id <= 0 { - return 1, err - } - - aiValue, err := table.AutoIncrColumn().ValueOf(bean) - if err != nil { - session.engine.logger.Errorf("%v", err) - } - - if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { - return 1, nil - } - - return 1, convertAssignV(aiValue.Addr(), id) - } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.URI().DBType == schemas.POSTGRES || - session.engine.dialect.URI().DBType == schemas.MSSQL) { - res, err := session.queryBytes(sqlStr, args...) - - if err != nil { - return 0, err - } - defer handleAfterInsertProcessorFunc(bean) - - session.cacheInsert(tableName) - - if table.Version != "" && session.statement.CheckVersion { - verValue, err := table.VersionColumn().ValueOf(bean) - if err != nil { - session.engine.logger.Errorf("%v", err) - } else if verValue.IsValid() && verValue.CanSet() { - session.incrVersionFieldValue(verValue) + var id int64 + if !rows.Next() { + if rows.Err() != nil { + return 0, rows.Err() } - } - - if len(res) < 1 { return 0, errors.New("insert successfully but not returned id") } - - idByte := res[0][table.AutoIncrement] - id, err := strconv.ParseInt(string(idByte), 10, 64) - if err != nil || id <= 0 { + if err := rows.Scan(&id); err != nil { return 1, err } - aiValue, err := table.AutoIncrColumn().ValueOf(bean) if err != nil { session.engine.logger.Errorf("%v", err) diff --git a/tags/parser.go b/tags/parser.go index 72baa153..9f9a8f62 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -242,6 +242,10 @@ func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, f } func (parser *Parser) parseField(table *schemas.Table, fieldIndex int, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { + if isNotTitle(field.Name) { + return nil, ErrIgnoreField + } + var ( tag = field.Tag ormTagStr = strings.TrimSpace(tag.Get(parser.identifier)) @@ -282,12 +286,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table.Name = names.GetTableName(parser.tableMapper, v) for i := 0; i < t.NumField(); i++ { - var field = t.Field(i) - if isNotTitle(field.Name) { - continue - } - - col, err := parser.parseField(table, i, field, v.Field(i)) + col, err := parser.parseField(table, i, t.Field(i), v.Field(i)) if err == ErrIgnoreField { continue } else if err != nil { diff --git a/tags/tag.go b/tags/tag.go index 641b8c52..32cdb37c 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -101,11 +101,12 @@ type Handler func(ctx *Context) error var ( // defaultTagHandlers enumerates all the default tag handler defaultTagHandlers = map[string]Handler{ + "-": IgnoreHandler, "<-": OnlyFromDBTagHandler, "->": OnlyToDBTagHandler, "PK": PKTagHandler, "NULL": NULLTagHandler, - "NOT": IgnoreTagHandler, + "NOT": NotTagHandler, "AUTOINCR": AutoIncrTagHandler, "DEFAULT": DefaultTagHandler, "CREATED": CreatedTagHandler, @@ -130,11 +131,16 @@ func init() { } } -// IgnoreTagHandler describes ignored tag handler -func IgnoreTagHandler(ctx *Context) error { +// NotTagHandler describes ignored tag handler +func NotTagHandler(ctx *Context) error { return nil } +// IgnoreHandler represetns the field should be ignored +func IgnoreHandler(ctx *Context) error { + return ErrIgnoreField +} + // OnlyFromDBTagHandler describes mapping direction tag handler func OnlyFromDBTagHandler(ctx *Context) error { ctx.col.MapType = schemas.ONLYFROMDB From 107bee4eb422d2975e6031e3d930c60b74015cb0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Jul 2021 11:46:41 +0800 Subject: [PATCH 094/179] refactor conversion (#2001) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2001 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- Makefile | 2 +- convert.go | 561 +------------------- convert/time.go | 49 -- integrations/session_get_test.go | 58 +- integrations/types_test.go | 2 +- internal/convert/bool.go | 51 ++ {convert => internal/convert}/conversion.go | 0 internal/convert/float.go | 142 +++++ internal/convert/int.go | 178 +++++++ {convert => internal/convert}/interface.go | 0 internal/convert/scanner.go | 19 + internal/convert/string.go | 75 +++ internal/convert/time.go | 108 ++++ {convert => internal/convert}/time_test.go | 0 internal/statements/statement.go | 2 +- internal/statements/update.go | 2 +- internal/statements/values.go | 2 +- scan.go | 10 +- session.go | 14 +- session_find.go | 6 +- session_get.go | 2 +- tags/parser.go | 2 +- 22 files changed, 610 insertions(+), 675 deletions(-) delete mode 100644 convert/time.go create mode 100644 internal/convert/bool.go rename {convert => internal/convert}/conversion.go (100%) create mode 100644 internal/convert/float.go create mode 100644 internal/convert/int.go rename {convert => internal/convert}/interface.go (100%) create mode 100644 internal/convert/scanner.go create mode 100644 internal/convert/string.go create mode 100644 internal/convert/time.go rename {convert => internal/convert}/time_test.go (100%) diff --git a/Makefile b/Makefile index 1bdd44c9..5675589d 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GOFMT ?= gofmt -s TAGS ?= SED_INPLACE := sed -i -GO_DIRS := caches contexts integrations convert core dialects internal log migrate names schemas tags +GO_DIRS := caches contexts integrations core dialects internal log migrate names schemas tags GOFILES := $(wildcard *.go) GOFILES += $(shell find $(GO_DIRS) -name "*.go" -type f) INTEGRATION_PACKAGES := xorm.io/xorm/integrations diff --git a/convert.go b/convert.go index c3eb4de9..c4fc7867 100644 --- a/convert.go +++ b/convert.go @@ -15,7 +15,7 @@ import ( "strconv" "time" - "xorm.io/xorm/convert" + "xorm.io/xorm/internal/convert" ) var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error @@ -36,347 +36,6 @@ func cloneBytes(b []byte) []byte { return c } -func asString(src interface{}) string { - switch v := src.(type) { - case string: - return v - case []byte: - return string(v) - case *sql.NullString: - return v.String - case *sql.NullInt32: - return fmt.Sprintf("%d", v.Int32) - case *sql.NullInt64: - return fmt.Sprintf("%d", v.Int64) - } - rv := reflect.ValueOf(src) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(rv.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(rv.Uint(), 10) - case reflect.Float64: - return strconv.FormatFloat(rv.Float(), 'g', -1, 64) - case reflect.Float32: - return strconv.FormatFloat(rv.Float(), 'g', -1, 32) - case reflect.Bool: - return strconv.FormatBool(rv.Bool()) - } - return fmt.Sprintf("%v", src) -} - -func asInt64(src interface{}) (int64, error) { - switch v := src.(type) { - case int: - return int64(v), nil - case int16: - return int64(v), nil - case int32: - return int64(v), nil - case int8: - return int64(v), nil - case int64: - return v, nil - case uint: - return int64(v), nil - case uint8: - return int64(v), nil - case uint16: - return int64(v), nil - case uint32: - return int64(v), nil - case uint64: - return int64(v), nil - case []byte: - return strconv.ParseInt(string(v), 10, 64) - case string: - return strconv.ParseInt(v, 10, 64) - case *sql.NullString: - return strconv.ParseInt(v.String, 10, 64) - case *sql.NullInt32: - return int64(v.Int32), nil - case *sql.NullInt64: - return int64(v.Int64), nil - } - - rv := reflect.ValueOf(src) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return int64(rv.Uint()), nil - case reflect.Float64, reflect.Float32: - return int64(rv.Float()), nil - case reflect.String: - return strconv.ParseInt(rv.String(), 10, 64) - } - return 0, fmt.Errorf("unsupported value %T as int64", src) -} - -func asUint64(src interface{}) (uint64, error) { - switch v := src.(type) { - case int: - return uint64(v), nil - case int16: - return uint64(v), nil - case int32: - return uint64(v), nil - case int8: - return uint64(v), nil - case int64: - return uint64(v), nil - case uint: - return uint64(v), nil - case uint8: - return uint64(v), nil - case uint16: - return uint64(v), nil - case uint32: - return uint64(v), nil - case uint64: - return v, nil - case []byte: - return strconv.ParseUint(string(v), 10, 64) - case string: - return strconv.ParseUint(v, 10, 64) - case *sql.NullString: - return strconv.ParseUint(v.String, 10, 64) - case *sql.NullInt32: - return uint64(v.Int32), nil - case *sql.NullInt64: - return uint64(v.Int64), nil - } - - rv := reflect.ValueOf(src) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return uint64(rv.Int()), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return uint64(rv.Uint()), nil - case reflect.Float64, reflect.Float32: - return uint64(rv.Float()), nil - case reflect.String: - return strconv.ParseUint(rv.String(), 10, 64) - } - return 0, fmt.Errorf("unsupported value %T as uint64", src) -} - -func asFloat64(src interface{}) (float64, error) { - switch v := src.(type) { - case int: - return float64(v), nil - case int16: - return float64(v), nil - case int32: - return float64(v), nil - case int8: - return float64(v), nil - case int64: - return float64(v), nil - case uint: - return float64(v), nil - case uint8: - return float64(v), nil - case uint16: - return float64(v), nil - case uint32: - return float64(v), nil - case uint64: - return float64(v), nil - case []byte: - return strconv.ParseFloat(string(v), 64) - case string: - return strconv.ParseFloat(v, 64) - case *sql.NullString: - return strconv.ParseFloat(v.String, 64) - case *sql.NullInt32: - return float64(v.Int32), nil - case *sql.NullInt64: - return float64(v.Int64), nil - case *sql.NullFloat64: - return v.Float64, nil - } - - rv := reflect.ValueOf(src) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float64(rv.Int()), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return float64(rv.Uint()), nil - case reflect.Float64, reflect.Float32: - return float64(rv.Float()), nil - case reflect.String: - return strconv.ParseFloat(rv.String(), 64) - } - return 0, fmt.Errorf("unsupported value %T as int64", src) -} - -func asBigFloat(src interface{}) (*big.Float, error) { - res := big.NewFloat(0) - switch v := src.(type) { - case int: - res.SetInt64(int64(v)) - return res, nil - case int16: - res.SetInt64(int64(v)) - return res, nil - case int32: - res.SetInt64(int64(v)) - return res, nil - case int8: - res.SetInt64(int64(v)) - return res, nil - case int64: - res.SetInt64(int64(v)) - return res, nil - case uint: - res.SetUint64(uint64(v)) - return res, nil - case uint8: - res.SetUint64(uint64(v)) - return res, nil - case uint16: - res.SetUint64(uint64(v)) - return res, nil - case uint32: - res.SetUint64(uint64(v)) - return res, nil - case uint64: - res.SetUint64(uint64(v)) - return res, nil - case []byte: - res.SetString(string(v)) - return res, nil - case string: - res.SetString(v) - return res, nil - case *sql.NullString: - if v.Valid { - res.SetString(v.String) - return res, nil - } - return nil, nil - case *sql.NullInt32: - if v.Valid { - res.SetInt64(int64(v.Int32)) - return res, nil - } - return nil, nil - case *sql.NullInt64: - if v.Valid { - res.SetInt64(int64(v.Int64)) - return res, nil - } - return nil, nil - } - - rv := reflect.ValueOf(src) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - res.SetInt64(rv.Int()) - return res, nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - res.SetUint64(rv.Uint()) - return res, nil - case reflect.Float64, reflect.Float32: - res.SetFloat64(rv.Float()) - return res, nil - case reflect.String: - res.SetString(rv.String()) - return res, nil - } - return nil, fmt.Errorf("unsupported value %T as big.Float", src) -} - -func asBytes(src interface{}) ([]byte, bool) { - switch t := src.(type) { - case []byte: - return t, true - case *sql.NullString: - if !t.Valid { - return nil, true - } - return []byte(t.String), true - case *sql.RawBytes: - return *t, true - } - - rv := reflect.ValueOf(src) - - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.AppendInt(nil, rv.Int(), 10), true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.AppendUint(nil, rv.Uint(), 10), true - case reflect.Float32: - return strconv.AppendFloat(nil, rv.Float(), 'g', -1, 32), true - case reflect.Float64: - return strconv.AppendFloat(nil, rv.Float(), 'g', -1, 64), true - case reflect.Bool: - return strconv.AppendBool(nil, rv.Bool()), true - case reflect.String: - return []byte(rv.String()), true - } - return nil, false -} - -func asTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.Time, error) { - switch t := src.(type) { - case string: - return convert.String2Time(t, dbLoc, uiLoc) - case *sql.NullString: - if !t.Valid { - return nil, nil - } - return convert.String2Time(t.String, dbLoc, uiLoc) - case []uint8: - if t == nil { - return nil, nil - } - return convert.String2Time(string(t), dbLoc, uiLoc) - case *sql.NullTime: - if !t.Valid { - return nil, nil - } - z, _ := t.Time.Zone() - if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() { - tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(), - t.Time.Minute(), t.Time.Second(), t.Time.Nanosecond(), dbLoc).In(uiLoc) - return &tm, nil - } - tm := t.Time.In(uiLoc) - return &tm, nil - case *time.Time: - z, _ := t.Zone() - if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { - tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), - t.Minute(), t.Second(), t.Nanosecond(), dbLoc).In(uiLoc) - return &tm, nil - } - tm := t.In(uiLoc) - return &tm, nil - case time.Time: - z, _ := t.Zone() - if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { - tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), - t.Minute(), t.Second(), t.Nanosecond(), dbLoc).In(uiLoc) - return &tm, nil - } - tm := t.In(uiLoc) - return &tm, nil - case int: - tm := time.Unix(int64(t), 0).In(uiLoc) - return &tm, nil - case int64: - tm := time.Unix(t, 0).In(uiLoc) - return &tm, nil - case *sql.NullInt64: - tm := time.Unix(t.Int64, 0).In(uiLoc) - return &tm, nil - } - return nil, fmt.Errorf("unsupported value %#v as time", src) -} - // convertAssign copies to dest the value in src, converting it if possible. // An error is returned if the copy would result in loss of information. // dest should be a pointer type. @@ -585,7 +244,7 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve } return nil } - case *NullUint32: + case *convert.NullUint32: switch d := dest.(type) { case *uint8: if s.Valid { @@ -603,7 +262,7 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve } return nil } - case *NullUint64: + case *convert.NullUint64: switch d := dest.(type) { case *uint64: if s.Valid { @@ -628,11 +287,11 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: - *d = asString(src) + *d = convert.AsString(src) return nil } case *[]byte: - if b, ok := asBytes(src); ok { + if b, ok := convert.AsBytes(src); ok { *d = b return nil } @@ -666,7 +325,7 @@ func convertAssignV(dv reflect.Value, src interface{}) error { } return convertAssignV(dv.Elem(), src) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - i64, err := asInt64(src) + i64, err := convert.AsInt64(src) if err != nil { err = strconvErr(err) return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) @@ -674,7 +333,7 @@ func convertAssignV(dv reflect.Value, src interface{}) error { dv.SetInt(i64) return nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - u64, err := asUint64(src) + u64, err := convert.AsUint64(src) if err != nil { err = strconvErr(err) return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) @@ -682,7 +341,7 @@ func convertAssignV(dv reflect.Value, src interface{}) error { dv.SetUint(u64) return nil case reflect.Float32, reflect.Float64: - f64, err := asFloat64(src) + f64, err := convert.AsFloat64(src) if err != nil { err = strconvErr(err) return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) @@ -690,17 +349,17 @@ func convertAssignV(dv reflect.Value, src interface{}) error { dv.SetFloat(f64) return nil case reflect.String: - dv.SetString(asString(src)) + dv.SetString(convert.AsString(src)) return nil case reflect.Bool: - b, err := asBool(src) + b, err := convert.AsBool(src) if err != nil { return err } dv.SetBool(b) return nil case reflect.Slice, reflect.Map, reflect.Struct, reflect.Array: - data, ok := asBytes(src) + data, ok := convert.AsBytes(src) if !ok { return fmt.Errorf("onvertAssignV: src cannot be as bytes %#v", src) } @@ -753,201 +412,3 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { } return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) } - -func asBool(src interface{}) (bool, error) { - switch v := src.(type) { - case bool: - return v, nil - case *bool: - return *v, nil - case *sql.NullBool: - return v.Bool, nil - case int64: - return v > 0, nil - case int: - return v > 0, nil - case int8: - return v > 0, nil - case int16: - return v > 0, nil - case int32: - return v > 0, nil - case []byte: - if len(v) == 0 { - return false, nil - } - if v[0] == 0x00 { - return false, nil - } else if v[0] == 0x01 { - return true, nil - } - return strconv.ParseBool(string(v)) - case string: - return strconv.ParseBool(v) - case *sql.NullInt64: - return v.Int64 > 0, nil - case *sql.NullInt32: - return v.Int32 > 0, nil - default: - return false, fmt.Errorf("unknow type %T as bool", src) - } -} - -// str2PK convert string value to primary key value according to tp -func str2PKValue(s string, tp reflect.Type) (reflect.Value, error) { - var err error - var result interface{} - var defReturn = reflect.Zero(tp) - - switch tp.Kind() { - case reflect.Int: - result, err = strconv.Atoi(s) - if err != nil { - return defReturn, fmt.Errorf("convert %s as int: %s", s, err.Error()) - } - case reflect.Int8: - x, err := strconv.Atoi(s) - if err != nil { - return defReturn, fmt.Errorf("convert %s as int8: %s", s, err.Error()) - } - result = int8(x) - case reflect.Int16: - x, err := strconv.Atoi(s) - if err != nil { - return defReturn, fmt.Errorf("convert %s as int16: %s", s, err.Error()) - } - result = int16(x) - case reflect.Int32: - x, err := strconv.Atoi(s) - if err != nil { - return defReturn, fmt.Errorf("convert %s as int32: %s", s, err.Error()) - } - result = int32(x) - case reflect.Int64: - result, err = strconv.ParseInt(s, 10, 64) - if err != nil { - return defReturn, fmt.Errorf("convert %s as int64: %s", s, err.Error()) - } - case reflect.Uint: - x, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return defReturn, fmt.Errorf("convert %s as uint: %s", s, err.Error()) - } - result = uint(x) - case reflect.Uint8: - x, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return defReturn, fmt.Errorf("convert %s as uint8: %s", s, err.Error()) - } - result = uint8(x) - case reflect.Uint16: - x, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return defReturn, fmt.Errorf("convert %s as uint16: %s", s, err.Error()) - } - result = uint16(x) - case reflect.Uint32: - x, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return defReturn, fmt.Errorf("convert %s as uint32: %s", s, err.Error()) - } - result = uint32(x) - case reflect.Uint64: - result, err = strconv.ParseUint(s, 10, 64) - if err != nil { - return defReturn, fmt.Errorf("convert %s as uint64: %s", s, err.Error()) - } - case reflect.String: - result = s - default: - return defReturn, errors.New("unsupported convert type") - } - return reflect.ValueOf(result).Convert(tp), nil -} - -func str2PK(s string, tp reflect.Type) (interface{}, error) { - v, err := str2PKValue(s, tp) - if err != nil { - return nil, err - } - return v.Interface(), nil -} - -var ( - _ sql.Scanner = &NullUint64{} -) - -// NullUint64 represents an uint64 that may be null. -// NullUint64 implements the Scanner interface so -// it can be used as a scan destination, similar to NullString. -type NullUint64 struct { - Uint64 uint64 - Valid bool -} - -// Scan implements the Scanner interface. -func (n *NullUint64) Scan(value interface{}) error { - if value == nil { - n.Uint64, n.Valid = 0, false - return nil - } - n.Valid = true - var err error - n.Uint64, err = asUint64(value) - return err -} - -// Value implements the driver Valuer interface. -func (n NullUint64) Value() (driver.Value, error) { - if !n.Valid { - return nil, nil - } - return n.Uint64, nil -} - -var ( - _ sql.Scanner = &NullUint32{} -) - -// NullUint32 represents an uint32 that may be null. -// NullUint32 implements the Scanner interface so -// it can be used as a scan destination, similar to NullString. -type NullUint32 struct { - Uint32 uint32 - Valid bool // Valid is true if Uint32 is not NULL -} - -// Scan implements the Scanner interface. -func (n *NullUint32) Scan(value interface{}) error { - if value == nil { - n.Uint32, n.Valid = 0, false - return nil - } - n.Valid = true - i64, err := asUint64(value) - if err != nil { - return err - } - n.Uint32 = uint32(i64) - return nil -} - -// Value implements the driver Valuer interface. -func (n NullUint32) Value() (driver.Value, error) { - if !n.Valid { - return nil, nil - } - return int64(n.Uint32), nil -} - -var ( - _ sql.Scanner = &EmptyScanner{} -) - -// EmptyScanner represents an empty scanner which will ignore the scan -type EmptyScanner struct{} - -// Scan implements sql.Scanner -func (EmptyScanner) Scan(value interface{}) error { - return nil -} diff --git a/convert/time.go b/convert/time.go deleted file mode 100644 index 6a53171b..00000000 --- a/convert/time.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2021 The Xorm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package convert - -import ( - "fmt" - "strconv" - "time" - - "xorm.io/xorm/internal/utils" -) - -// String2Time converts a string to time with original location -func String2Time(s string, originalLocation *time.Location, convertedLocation *time.Location) (*time.Time, error) { - if len(s) == 19 { - if s == utils.ZeroTime0 || s == utils.ZeroTime1 { - return &time.Time{}, nil - } - dt, err := time.ParseInLocation("2006-01-02 15:04:05", s, originalLocation) - if err != nil { - return nil, err - } - dt = dt.In(convertedLocation) - return &dt, nil - } else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' { - dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation) - if err != nil { - return nil, err - } - dt = dt.In(convertedLocation) - return &dt, nil - } else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' { - dt, err := time.Parse(time.RFC3339, s) - if err != nil { - return nil, err - } - dt = dt.In(convertedLocation) - return &dt, nil - } else { - i, err := strconv.ParseInt(s, 10, 64) - if err == nil { - tm := time.Unix(i, 0).In(convertedLocation) - return &tm, nil - } - } - return nil, fmt.Errorf("unsupported conversion from %s to time", s) -} diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index d3ce2a11..c0376c70 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -9,68 +9,18 @@ import ( "errors" "fmt" "math/big" - "strconv" "testing" "time" "xorm.io/xorm" "xorm.io/xorm/contexts" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/schemas" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" ) -func convertInt(v interface{}) (int64, error) { - switch v.(type) { - case int: - return int64(v.(int)), nil - case int8: - return int64(v.(int8)), nil - case int16: - return int64(v.(int16)), nil - case int32: - return int64(v.(int32)), nil - case int64: - return v.(int64), nil - case []byte: - i, err := strconv.ParseInt(string(v.([]byte)), 10, 64) - if err != nil { - return 0, err - } - return i, nil - case string: - i, err := strconv.ParseInt(v.(string), 10, 64) - if err != nil { - return 0, err - } - return i, nil - } - return 0, fmt.Errorf("unsupported type: %v", v) -} - -func convertFloat(v interface{}) (float64, error) { - switch v.(type) { - case float32: - return float64(v.(float32)), nil - case float64: - return v.(float64), nil - case string: - i, err := strconv.ParseFloat(v.(string), 64) - if err != nil { - return 0, err - } - return i, nil - case []byte: - i, err := strconv.ParseFloat(string(v.([]byte)), 64) - if err != nil { - return 0, err - } - return i, nil - } - return 0, fmt.Errorf("unsupported type: %v", v) -} - func TestGetVar(t *testing.T) { assert.NoError(t, PrepareEngine()) @@ -261,17 +211,17 @@ func TestGetVar(t *testing.T) { assert.NoError(t, err) assert.Equal(t, true, has) - v1, err := convertInt(valuesSliceInter[0]) + v1, err := convert.AsInt64(valuesSliceInter[0]) assert.NoError(t, err) assert.EqualValues(t, 1, v1) assert.Equal(t, "hi", fmt.Sprintf("%s", valuesSliceInter[1])) - v3, err := convertInt(valuesSliceInter[2]) + v3, err := convert.AsInt64(valuesSliceInter[2]) assert.NoError(t, err) assert.EqualValues(t, 28, v3) - v4, err := convertFloat(valuesSliceInter[3]) + v4, err := convert.AsFloat64(valuesSliceInter[3]) assert.NoError(t, err) assert.Equal(t, "1.5", fmt.Sprintf("%v", v4)) } diff --git a/integrations/types_test.go b/integrations/types_test.go index f192c1ff..9d4e46a0 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -13,7 +13,7 @@ import ( "testing" "xorm.io/xorm" - "xorm.io/xorm/convert" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/schemas" diff --git a/internal/convert/bool.go b/internal/convert/bool.go new file mode 100644 index 00000000..58b23f4b --- /dev/null +++ b/internal/convert/bool.go @@ -0,0 +1,51 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "database/sql" + "fmt" + "strconv" +) + +// AsBool convert interface as bool +func AsBool(src interface{}) (bool, error) { + switch v := src.(type) { + case bool: + return v, nil + case *bool: + return *v, nil + case *sql.NullBool: + return v.Bool, nil + case int64: + return v > 0, nil + case int: + return v > 0, nil + case int8: + return v > 0, nil + case int16: + return v > 0, nil + case int32: + return v > 0, nil + case []byte: + if len(v) == 0 { + return false, nil + } + if v[0] == 0x00 { + return false, nil + } else if v[0] == 0x01 { + return true, nil + } + return strconv.ParseBool(string(v)) + case string: + return strconv.ParseBool(v) + case *sql.NullInt64: + return v.Int64 > 0, nil + case *sql.NullInt32: + return v.Int32 > 0, nil + default: + return false, fmt.Errorf("unknow type %T as bool", src) + } +} diff --git a/convert/conversion.go b/internal/convert/conversion.go similarity index 100% rename from convert/conversion.go rename to internal/convert/conversion.go diff --git a/internal/convert/float.go b/internal/convert/float.go new file mode 100644 index 00000000..51b441ce --- /dev/null +++ b/internal/convert/float.go @@ -0,0 +1,142 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "database/sql" + "fmt" + "math/big" + "reflect" + "strconv" +) + +// AsFloat64 convets interface as float64 +func AsFloat64(src interface{}) (float64, error) { + switch v := src.(type) { + case int: + return float64(v), nil + case int16: + return float64(v), nil + case int32: + return float64(v), nil + case int8: + return float64(v), nil + case int64: + return float64(v), nil + case uint: + return float64(v), nil + case uint8: + return float64(v), nil + case uint16: + return float64(v), nil + case uint32: + return float64(v), nil + case uint64: + return float64(v), nil + case []byte: + return strconv.ParseFloat(string(v), 64) + case string: + return strconv.ParseFloat(v, 64) + case *sql.NullString: + return strconv.ParseFloat(v.String, 64) + case *sql.NullInt32: + return float64(v.Int32), nil + case *sql.NullInt64: + return float64(v.Int64), nil + case *sql.NullFloat64: + return v.Float64, nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(rv.Uint()), nil + case reflect.Float64, reflect.Float32: + return float64(rv.Float()), nil + case reflect.String: + return strconv.ParseFloat(rv.String(), 64) + } + return 0, fmt.Errorf("unsupported value %T as int64", src) +} + +// AsBigFloat converts interface as big.Float +func AsBigFloat(src interface{}) (*big.Float, error) { + res := big.NewFloat(0) + switch v := src.(type) { + case int: + res.SetInt64(int64(v)) + return res, nil + case int16: + res.SetInt64(int64(v)) + return res, nil + case int32: + res.SetInt64(int64(v)) + return res, nil + case int8: + res.SetInt64(int64(v)) + return res, nil + case int64: + res.SetInt64(int64(v)) + return res, nil + case uint: + res.SetUint64(uint64(v)) + return res, nil + case uint8: + res.SetUint64(uint64(v)) + return res, nil + case uint16: + res.SetUint64(uint64(v)) + return res, nil + case uint32: + res.SetUint64(uint64(v)) + return res, nil + case uint64: + res.SetUint64(uint64(v)) + return res, nil + case []byte: + res.SetString(string(v)) + return res, nil + case string: + res.SetString(v) + return res, nil + case *sql.NullString: + if v.Valid { + res.SetString(v.String) + return res, nil + } + return nil, nil + case *sql.NullInt32: + if v.Valid { + res.SetInt64(int64(v.Int32)) + return res, nil + } + return nil, nil + case *sql.NullInt64: + if v.Valid { + res.SetInt64(int64(v.Int64)) + return res, nil + } + return nil, nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + res.SetInt64(rv.Int()) + return res, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + res.SetUint64(rv.Uint()) + return res, nil + case reflect.Float64, reflect.Float32: + res.SetFloat64(rv.Float()) + return res, nil + case reflect.String: + res.SetString(rv.String()) + return res, nil + } + return nil, fmt.Errorf("unsupported value %T as big.Float", src) +} diff --git a/internal/convert/int.go b/internal/convert/int.go new file mode 100644 index 00000000..af8d4f75 --- /dev/null +++ b/internal/convert/int.go @@ -0,0 +1,178 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + "strconv" +) + +// AsInt64 converts interface as int64 +func AsInt64(src interface{}) (int64, error) { + switch v := src.(type) { + case int: + return int64(v), nil + case int16: + return int64(v), nil + case int32: + return int64(v), nil + case int8: + return int64(v), nil + case int64: + return v, nil + case uint: + return int64(v), nil + case uint8: + return int64(v), nil + case uint16: + return int64(v), nil + case uint32: + return int64(v), nil + case uint64: + return int64(v), nil + case []byte: + return strconv.ParseInt(string(v), 10, 64) + case string: + return strconv.ParseInt(v, 10, 64) + case *sql.NullString: + return strconv.ParseInt(v.String, 10, 64) + case *sql.NullInt32: + return int64(v.Int32), nil + case *sql.NullInt64: + return int64(v.Int64), nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int64(rv.Uint()), nil + case reflect.Float64, reflect.Float32: + return int64(rv.Float()), nil + case reflect.String: + return strconv.ParseInt(rv.String(), 10, 64) + } + return 0, fmt.Errorf("unsupported value %T as int64", src) +} + +// AsUint64 converts interface as uint64 +func AsUint64(src interface{}) (uint64, error) { + switch v := src.(type) { + case int: + return uint64(v), nil + case int16: + return uint64(v), nil + case int32: + return uint64(v), nil + case int8: + return uint64(v), nil + case int64: + return uint64(v), nil + case uint: + return uint64(v), nil + case uint8: + return uint64(v), nil + case uint16: + return uint64(v), nil + case uint32: + return uint64(v), nil + case uint64: + return v, nil + case []byte: + return strconv.ParseUint(string(v), 10, 64) + case string: + return strconv.ParseUint(v, 10, 64) + case *sql.NullString: + return strconv.ParseUint(v.String, 10, 64) + case *sql.NullInt32: + return uint64(v.Int32), nil + case *sql.NullInt64: + return uint64(v.Int64), nil + } + + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return uint64(rv.Uint()), nil + case reflect.Float64, reflect.Float32: + return uint64(rv.Float()), nil + case reflect.String: + return strconv.ParseUint(rv.String(), 10, 64) + } + return 0, fmt.Errorf("unsupported value %T as uint64", src) +} + +var ( + _ sql.Scanner = &NullUint64{} +) + +// NullUint64 represents an uint64 that may be null. +// NullUint64 implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullUint64 struct { + Uint64 uint64 + Valid bool +} + +// Scan implements the Scanner interface. +func (n *NullUint64) Scan(value interface{}) error { + if value == nil { + n.Uint64, n.Valid = 0, false + return nil + } + n.Valid = true + var err error + n.Uint64, err = AsUint64(value) + return err +} + +// Value implements the driver Valuer interface. +func (n NullUint64) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Uint64, nil +} + +var ( + _ sql.Scanner = &NullUint32{} +) + +// NullUint32 represents an uint32 that may be null. +// NullUint32 implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullUint32 struct { + Uint32 uint32 + Valid bool // Valid is true if Uint32 is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullUint32) Scan(value interface{}) error { + if value == nil { + n.Uint32, n.Valid = 0, false + return nil + } + n.Valid = true + i64, err := AsUint64(value) + if err != nil { + return err + } + n.Uint32 = uint32(i64) + return nil +} + +// Value implements the driver Valuer interface. +func (n NullUint32) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return int64(n.Uint32), nil +} diff --git a/convert/interface.go b/internal/convert/interface.go similarity index 100% rename from convert/interface.go rename to internal/convert/interface.go diff --git a/internal/convert/scanner.go b/internal/convert/scanner.go new file mode 100644 index 00000000..505d3be0 --- /dev/null +++ b/internal/convert/scanner.go @@ -0,0 +1,19 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import "database/sql" + +var ( + _ sql.Scanner = &EmptyScanner{} +) + +// EmptyScanner represents an empty scanner which will ignore the scan +type EmptyScanner struct{} + +// Scan implements sql.Scanner +func (EmptyScanner) Scan(value interface{}) error { + return nil +} diff --git a/internal/convert/string.go b/internal/convert/string.go new file mode 100644 index 00000000..de11fa01 --- /dev/null +++ b/internal/convert/string.go @@ -0,0 +1,75 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "database/sql" + "fmt" + "reflect" + "strconv" +) + +// AsString converts interface as string +func AsString(src interface{}) string { + switch v := src.(type) { + case string: + return v + case []byte: + return string(v) + case *sql.NullString: + return v.String + case *sql.NullInt32: + return fmt.Sprintf("%d", v.Int32) + case *sql.NullInt64: + return fmt.Sprintf("%d", v.Int64) + } + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'g', -1, 64) + case reflect.Float32: + return strconv.FormatFloat(rv.Float(), 'g', -1, 32) + case reflect.Bool: + return strconv.FormatBool(rv.Bool()) + } + return fmt.Sprintf("%v", src) +} + +// AsBytes converts interface as bytes +func AsBytes(src interface{}) ([]byte, bool) { + switch t := src.(type) { + case []byte: + return t, true + case *sql.NullString: + if !t.Valid { + return nil, true + } + return []byte(t.String), true + case *sql.RawBytes: + return *t, true + } + + rv := reflect.ValueOf(src) + + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.AppendInt(nil, rv.Int(), 10), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.AppendUint(nil, rv.Uint(), 10), true + case reflect.Float32: + return strconv.AppendFloat(nil, rv.Float(), 'g', -1, 32), true + case reflect.Float64: + return strconv.AppendFloat(nil, rv.Float(), 'g', -1, 64), true + case reflect.Bool: + return strconv.AppendBool(nil, rv.Bool()), true + case reflect.String: + return []byte(rv.String()), true + } + return nil, false +} diff --git a/internal/convert/time.go b/internal/convert/time.go new file mode 100644 index 00000000..ecb30a3f --- /dev/null +++ b/internal/convert/time.go @@ -0,0 +1,108 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "database/sql" + "fmt" + "strconv" + "time" + + "xorm.io/xorm/internal/utils" +) + +// String2Time converts a string to time with original location +func String2Time(s string, originalLocation *time.Location, convertedLocation *time.Location) (*time.Time, error) { + if len(s) == 19 { + if s == utils.ZeroTime0 || s == utils.ZeroTime1 { + return &time.Time{}, nil + } + dt, err := time.ParseInLocation("2006-01-02 15:04:05", s, originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil + } else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' { + dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil + } else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' { + dt, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil + } else { + i, err := strconv.ParseInt(s, 10, 64) + if err == nil { + tm := time.Unix(i, 0).In(convertedLocation) + return &tm, nil + } + } + return nil, fmt.Errorf("unsupported conversion from %s to time", s) +} + +// AsTime converts interface as time +func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.Time, error) { + switch t := src.(type) { + case string: + return String2Time(t, dbLoc, uiLoc) + case *sql.NullString: + if !t.Valid { + return nil, nil + } + return String2Time(t.String, dbLoc, uiLoc) + case []uint8: + if t == nil { + return nil, nil + } + return String2Time(string(t), dbLoc, uiLoc) + case *sql.NullTime: + if !t.Valid { + return nil, nil + } + z, _ := t.Time.Zone() + if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() { + tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(), + t.Time.Minute(), t.Time.Second(), t.Time.Nanosecond(), dbLoc).In(uiLoc) + return &tm, nil + } + tm := t.Time.In(uiLoc) + return &tm, nil + case *time.Time: + z, _ := t.Zone() + if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { + tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t.Minute(), t.Second(), t.Nanosecond(), dbLoc).In(uiLoc) + return &tm, nil + } + tm := t.In(uiLoc) + return &tm, nil + case time.Time: + z, _ := t.Zone() + if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { + tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t.Minute(), t.Second(), t.Nanosecond(), dbLoc).In(uiLoc) + return &tm, nil + } + tm := t.In(uiLoc) + return &tm, nil + case int: + tm := time.Unix(int64(t), 0).In(uiLoc) + return &tm, nil + case int64: + tm := time.Unix(t, 0).In(uiLoc) + return &tm, nil + case *sql.NullInt64: + tm := time.Unix(t.Int64, 0).In(uiLoc) + return &tm, nil + } + return nil, fmt.Errorf("unsupported value %#v as time", src) +} diff --git a/convert/time_test.go b/internal/convert/time_test.go similarity index 100% rename from convert/time_test.go rename to internal/convert/time_test.go diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 0e245a96..8e3c083c 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -15,8 +15,8 @@ import ( "xorm.io/builder" "xorm.io/xorm/contexts" - "xorm.io/xorm/convert" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" diff --git a/internal/statements/update.go b/internal/statements/update.go index 3020595b..be6ed885 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -11,8 +11,8 @@ import ( "reflect" "time" - "xorm.io/xorm/convert" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" diff --git a/internal/statements/values.go b/internal/statements/values.go index c572ead5..ada01755 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -12,8 +12,8 @@ import ( "reflect" "time" - "xorm.io/xorm/convert" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/schemas" ) diff --git a/scan.go b/scan.go index ccd6938d..83ad0b02 100644 --- a/scan.go +++ b/scan.go @@ -11,9 +11,9 @@ import ( "reflect" "time" - "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/schemas" ) @@ -35,9 +35,9 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { case *int64: return &sql.NullInt64{}, true, nil case *uint, *uint8, *uint16, *uint32: - return &NullUint32{}, true, nil + return &convert.NullUint32{}, true, nil case *uint64: - return &NullUint64{}, true, nil + return &convert.NullUint64{}, true, nil case *float32, *float64: return &sql.NullFloat64{}, true, nil case *bool: @@ -63,9 +63,9 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { case reflect.Int32, reflect.Int, reflect.Int16, reflect.Int8: return &sql.NullInt32{}, true, nil case reflect.Uint64: - return &NullUint64{}, true, nil + return &convert.NullUint64{}, true, nil case reflect.Uint32, reflect.Uint, reflect.Uint16, reflect.Uint8: - return &NullUint32{}, true, nil + return &convert.NullUint32{}, true, nil default: return nil, false, fmt.Errorf("unsupported type: %#v", bean) } diff --git a/session.go b/session.go index 62d6a770..a15f5c3c 100644 --- a/session.go +++ b/session.go @@ -18,8 +18,8 @@ import ( "strings" "xorm.io/xorm/contexts" - "xorm.io/xorm/convert" "xorm.io/xorm/core" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/statements" "xorm.io/xorm/log" @@ -435,7 +435,7 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, types []*sql } func (session *Session) setJSON(fieldValue *reflect.Value, fieldType reflect.Type, scanResult interface{}) error { - bs, ok := asBytes(scanResult) + bs, ok := convert.AsBytes(scanResult) if !ok { return fmt.Errorf("unsupported database data type: %#v", scanResult) } @@ -476,7 +476,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec if fieldValue.CanAddr() { if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - data, ok := asBytes(scanResult) + data, ok := convert.AsBytes(scanResult) if !ok { return fmt.Errorf("cannot convert %#v as bytes", scanResult) } @@ -485,7 +485,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { - data, ok := asBytes(scanResult) + data, ok := convert.AsBytes(scanResult) if !ok { return fmt.Errorf("cannot convert %#v as bytes", scanResult) } @@ -525,7 +525,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec case reflect.Complex64, reflect.Complex128: return session.setJSON(fieldValue, fieldType, scanResult) case reflect.Slice, reflect.Array: - bs, ok := asBytes(scanResult) + bs, ok := convert.AsBytes(scanResult) if ok && fieldType.Elem().Kind() == reflect.Uint8 { if col.SQLType.IsText() { x := reflect.New(fieldType) @@ -551,7 +551,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } case reflect.Struct: if fieldType.ConvertibleTo(schemas.BigFloatType) { - v, err := asBigFloat(scanResult) + v, err := convert.AsBigFloat(scanResult) if err != nil { return err } @@ -565,7 +565,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec dbTZ = col.TimeZone } - t, err := asTime(scanResult, dbTZ, session.engine.TZLocation) + t, err := convert.AsTime(scanResult, dbTZ, session.engine.TZLocation) if err != nil { return err } diff --git a/session_find.go b/session_find.go index 010ecd6c..2f5438fb 100644 --- a/session_find.go +++ b/session_find.go @@ -6,7 +6,6 @@ package xorm import ( "errors" - "fmt" "reflect" "xorm.io/builder" @@ -476,12 +475,13 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } else if sliceValue.Kind() == reflect.Map { var key = ids[j] keyType := sliceValue.Type().Key() + keyValue := reflect.New(keyType) var ikey interface{} if len(key) == 1 { - ikey, err = str2PK(fmt.Sprintf("%v", key[0]), keyType) - if err != nil { + if err := convertAssignV(keyValue, key[0]); err != nil { return err } + ikey = keyValue.Elem().Interface() } else { if keyType.Kind() != reflect.Slice { return errors.New("table have multiple primary keys, key is not schemas.PK or slice") diff --git a/session_get.go b/session_get.go index 08172524..617ca169 100644 --- a/session_get.go +++ b/session_get.go @@ -15,8 +15,8 @@ import ( "time" "xorm.io/xorm/caches" - "xorm.io/xorm/convert" "xorm.io/xorm/core" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) diff --git a/tags/parser.go b/tags/parser.go index 9f9a8f62..7d8c3bd6 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -14,8 +14,8 @@ import ( "unicode" "xorm.io/xorm/caches" - "xorm.io/xorm/convert" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/names" "xorm.io/xorm/schemas" ) From 0f3f4ea7fbe2308d4dd8f5d5abe8477e0750dcd8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Jul 2021 19:17:50 +0800 Subject: [PATCH 095/179] Update docs (#2003) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2003 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- README.md | 2 ++ README_CN.md | 2 ++ doc.go | 68 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 40826f13..d7217fa2 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,13 @@ Drivers for Go's sql package which currently support database/sql includes: * [SQLite](https://sqlite.org) - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) + - [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) (windows unsupported) * MsSql - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) * Oracle + - [github.com/godror/godror)](https://github.com/godror/godror) (experiment) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) ## Installation diff --git a/README_CN.md b/README_CN.md index 06706417..36930faa 100644 --- a/README_CN.md +++ b/README_CN.md @@ -43,11 +43,13 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更: * [SQLite](https://sqlite.org) - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) + - [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) (Windows试验性支持) * MsSql - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) * Oracle + - [github.com/godror/godror)](https://github.com/godror/godror) (试验性支持) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持) ## 安装 diff --git a/doc.go b/doc.go index ea6a2226..d0653232 100644 --- a/doc.go +++ b/doc.go @@ -14,23 +14,30 @@ Make sure you have installed Go 1.11+ and then: Create Engine -Firstly, we should new an engine for a database +Firstly, we should create an engine for a database engine, err := xorm.NewEngine(driverName, dataSourceName) -Method NewEngine's parameters is the same as sql.Open. It depends -drivers' implementation. -Generally, one engine for an application is enough. You can set it as package variable. +Method NewEngine's parameters are the same as sql.Open which depend drivers' implementation. +Generally, one engine for an application is enough. You can define it as a package variable. Raw Methods -XORM also support raw SQL execution: +XORM supports raw SQL execution: -1. query a SQL string, the returned results is []map[string][]byte +1. query with a SQL string, the returned results is []map[string][]byte results, err := engine.Query("select * from user") -2. execute a SQL string, the returned results +2. query with a SQL string, the returned results is []map[string]string + + results, err := engine.QueryString("select * from user") + +3. query with a SQL string, the returned results is []map[string]interface{} + + results, err := engine.QueryInterface("select * from user") + +4. execute with a SQL string, the returned results affected, err := engine.Exec("update user set .... where ...") @@ -77,7 +84,9 @@ There are 8 major ORM methods and many helpful methods to use to operate databas 4. Query multiple records and record by record handle, there two methods, one is Iterate, another is Rows - err := engine.Iterate(...) + err := engine.Iterate(new(User), func(i int, bean interface{}) error { + // do something + }) // SELECT * FROM user rows, err := engine.Rows(...) @@ -120,7 +129,7 @@ another is Rows Conditions The above 8 methods could use with condition methods chainable. -Attention: the above 8 methods should be the last chainable method. +Notice: the above 8 methods should be the last chainable method. 1. ID, In @@ -179,6 +188,47 @@ Attention: the above 8 methods should be the last chainable method. engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users) //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id +Builder + +xorm could work with xorm.io/builder directly. + +1. With Where + + var cond = builder.Eq{"a":1, "b":2} + engine.Where(cond).Find(&users) + +2. With In + + var subQuery = builder.Select("name").From("group") + engine.In("group_name", subQuery).Find(&users) + +3. With Join + + var subQuery = builder.Select("name").From("group") + engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users) + +4. With SetExprs + + var subQuery = builder.Select("name").From("group") + engine.ID(1).SetExprs("name", subQuery).Update(new(User)) + +5. With SQL + + var query = builder.Select("name").From("group") + results, err := engine.SQL(query).Find(&groups) + +6. With Query + + var query = builder.Select("name").From("group") + results, err := engine.Query(query) + results, err := engine.QueryString(query) + results, err := engine.QueryInterface(query) + +7. With Exec + + var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2") + results, err := engine.Exec(query) + More usage, please visit http://xorm.io/docs */ package xorm From 47cfe0347f09113961bf844c719e3ff018e561fe Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Jul 2021 21:13:15 +0800 Subject: [PATCH 096/179] Add test for limit with query (#1787) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1787 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_query_test.go | 22 ++++++++++++++++++++++ internal/statements/query.go | 3 +++ 2 files changed, 25 insertions(+) diff --git a/integrations/session_query_test.go b/integrations/session_query_test.go index ed03ff3e..ef2ccdd6 100644 --- a/integrations/session_query_test.go +++ b/integrations/session_query_test.go @@ -396,3 +396,25 @@ func TestJoinWithSubQuery(t *testing.T) { assert.EqualValues(t, 1, len(querys)) assert.EqualValues(t, q, querys[0]) } + +func TestQueryStringWithLimit(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + if testEngine.Dialect().URI().DBType == schemas.MSSQL { + t.SkipNow() + return + } + + type QueryWithLimit struct { + Id int64 `xorm:"autoincr pk"` + Msg string `xorm:"varchar(255)"` + DepartId int64 + Money float32 + } + + assert.NoError(t, testEngine.Sync2(new(QueryWithLimit))) + + data, err := testEngine.Table("query_with_limit").Limit(20, 20).QueryString() + assert.NoError(t, err) + assert.EqualValues(t, 0, len(data)) +} diff --git a/internal/statements/query.go b/internal/statements/query.go index 69f48e73..76946cbd 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -247,6 +247,9 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB top = fmt.Sprintf("TOP %d ", LimitNValue) } if statement.Start > 0 { + if statement.RefTable == nil { + return "", nil, errors.New("Unsupported query limit without reference table") + } var column string if len(statement.RefTable.PKColumns()) == 0 { for _, index := range statement.RefTable.Indexes { From 6f79e06376c5e3ad591aa5b30dc7f0418c8f838d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 22 Jul 2021 11:07:53 +0800 Subject: [PATCH 097/179] Fix master/slave bug (#2004) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2004 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- engine_group.go | 28 ++++++++++++++++++++++++++++ session_raw.go | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/engine_group.go b/engine_group.go index 3569690b..f2fe913d 100644 --- a/engine_group.go +++ b/engine_group.go @@ -237,3 +237,31 @@ func (eg *EngineGroup) Slave() *Engine { func (eg *EngineGroup) Slaves() []*Engine { return eg.slaves } + +// Query execcute a select SQL and return the result +func (eg *EngineGroup) Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) { + sess := eg.NewSession() + sess.isAutoClose = true + return sess.Query(sqlOrArgs...) +} + +// QueryInterface execcute a select SQL and return the result +func (eg *EngineGroup) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) { + sess := eg.NewSession() + sess.isAutoClose = true + return sess.QueryInterface(sqlOrArgs...) +} + +// QueryString execcute a select SQL and return the result +func (eg *EngineGroup) QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) { + sess := eg.NewSession() + sess.isAutoClose = true + return sess.QueryString(sqlOrArgs...) +} + +// Rows execcute a select SQL and return the result +func (eg *EngineGroup) Rows(bean interface{}) (*Rows, error) { + sess := eg.NewSession() + sess.isAutoClose = true + return sess.Rows(bean) +} diff --git a/session_raw.go b/session_raw.go index 7eb8585d..2b488988 100644 --- a/session_raw.go +++ b/session_raw.go @@ -6,6 +6,7 @@ package xorm import ( "database/sql" + "strings" "xorm.io/xorm/core" ) @@ -32,7 +33,7 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row if session.isAutoCommit { var db *core.DB - if session.sessionType == groupSession { + if session.sessionType == groupSession && strings.EqualFold(sqlStr[:6], "select") { db = session.engine.engineGroup.Slave().DB() } else { db = session.DB() From ad4830f531cdf18cb742d5dd4f810b411931af1f Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 23 Jul 2021 08:59:53 +0800 Subject: [PATCH 098/179] Remove default length of 50 for Blob (#1959) There is an odd inconsistency with default blob sizes - this PR only sets the default size for bytea and binary. Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1959 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/mssql.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index 7deade80..3b935396 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -303,11 +303,16 @@ func (db *mssql) SQLType(c *schemas.Column) string { c.IsPrimaryKey = true c.Nullable = false res = schemas.BigInt - case schemas.Bytea, schemas.Blob, schemas.Binary, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: + case schemas.Bytea, schemas.Binary: res = schemas.VarBinary if c.Length == 0 { c.Length = 50 } + case schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: + res = schemas.VarBinary + if c.Length == 0 { + res += "(MAX)" + } case schemas.TimeStamp: res = schemas.DateTime case schemas.TimeStampz: From d973423802b86e23ba31dbbdbf332cfe967c910c Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 23 Jul 2021 09:01:49 +0800 Subject: [PATCH 099/179] Fix issue with byte representation in MSSQL (#1957) There is a missing cast to string in BuildUpdates which leads to a failure to call str2ucs and ucs2str for converts on MSSQL. Ref: https://github.com/go-gitea/gitea/issues/16252 Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/1957 Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- internal/statements/update.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/statements/update.go b/internal/statements/update.go index be6ed885..39a7f829 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -129,6 +129,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, } if data != nil { val = data + if !col.SQLType.IsBlob() { + val = string(data) + } } goto APPEND } @@ -141,6 +144,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, } if data != nil { val = data + if !col.SQLType.IsBlob() { + val = string(data) + } } goto APPEND } From c02a1bf00c44574eeff0e86c16138fb94a47baae Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 28 Jul 2021 10:03:09 +0800 Subject: [PATCH 100/179] Fix postgres driver datasource name parse (#2012) Fix #2010 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2012 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/postgres.go | 92 ++++++++++++++++++++++++++++++--------- dialects/postgres_test.go | 18 +++++--- 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 8a0dd7a8..cf760e18 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1341,14 +1341,6 @@ type pqDriver struct { type values map[string]string -func (vs values) Set(k, v string) { - vs[k] = v -} - -func (vs values) Get(k string) (v string) { - return vs[k] -} - func parseURL(connstr string) (string, error) { u, err := url.Parse(connstr) if err != nil { @@ -1368,20 +1360,74 @@ func parseURL(connstr string) (string, error) { return "", nil } -func parseOpts(name string, o values) error { - if len(name) == 0 { - return fmt.Errorf("invalid options: %s", name) +func parseOpts(urlStr string, o values) error { + if len(urlStr) == 0 { + return fmt.Errorf("invalid options: %s", urlStr) } - name = strings.TrimSpace(name) + urlStr = strings.TrimSpace(urlStr) - ps := strings.Split(name, " ") - for _, p := range ps { - kv := strings.Split(p, "=") - if len(kv) < 2 { - return fmt.Errorf("invalid option: %q", p) + var ( + inQuote bool + state int // 0 key, 1 space, 2 value, 3 equal + start int + key string + ) + for i, c := range urlStr { + switch c { + case ' ': + if !inQuote { + if state == 2 { + state = 1 + v := urlStr[start:i] + if strings.HasPrefix(v, "'") && strings.HasSuffix(v, "'") { + v = v[1 : len(v)-1] + } else if strings.HasPrefix(v, "'") || strings.HasSuffix(v, "'") { + return fmt.Errorf("wrong single quote in %d of %s", i, urlStr) + } + o[key] = v + } else if state != 1 { + return fmt.Errorf("wrong format: %v", urlStr) + } + } + case '\'': + if state == 3 { + state = 2 + start = i + } else if state != 2 { + return fmt.Errorf("wrong format: %v", urlStr) + } + inQuote = !inQuote + case '=': + if !inQuote { + if state != 0 { + return fmt.Errorf("wrong format: %v", urlStr) + } + key = urlStr[start:i] + state = 3 + } + default: + if state == 3 { + state = 2 + start = i + } else if state == 1 { + state = 0 + start = i + } + } + + if i == len(urlStr)-1 { + if state != 2 { + return errors.New("no value matched key") + } + v := urlStr[start : i+1] + if strings.HasPrefix(v, "'") && strings.HasSuffix(v, "'") { + v = v[1 : len(v)-1] + } else if strings.HasPrefix(v, "'") || strings.HasSuffix(v, "'") { + return fmt.Errorf("wrong single quote in %d of %s", i, urlStr) + } + o[key] = v } - o.Set(kv[0], kv[1]) } return nil @@ -1395,9 +1441,13 @@ func (p *pqDriver) Features() *DriverFeatures { func (p *pqDriver) Parse(driverName, dataSourceName string) (*URI, error) { db := &URI{DBType: schemas.POSTGRES} - var err error - if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") { + var err error + if strings.Contains(dataSourceName, "://") { + if !strings.HasPrefix(dataSourceName, "postgresql://") && !strings.HasPrefix(dataSourceName, "postgres://") { + return nil, fmt.Errorf("unsupported protocol %v", dataSourceName) + } + db.DBName, err = parseURL(dataSourceName) if err != nil { return nil, err @@ -1409,7 +1459,7 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*URI, error) { return nil, err } - db.DBName = o.Get("dbname") + db.DBName = o["dbname"] } if db.DBName == "" { diff --git a/dialects/postgres_test.go b/dialects/postgres_test.go index e0c36f92..bed8f307 100644 --- a/dialects/postgres_test.go +++ b/dialects/postgres_test.go @@ -22,20 +22,24 @@ func TestParsePostgres(t *testing.T) { //{"postgres://auser:パスワード@localhost:5432/データベース?sslmode=disable", "データベース", true}, {"dbname=db sslmode=disable", "db", true}, {"user=auser password=password dbname=db sslmode=disable", "db", true}, + {"user=auser password='pass word' dbname=db sslmode=disable", "db", true}, + {"user=auser password='pass word' sslmode=disable dbname='db'", "db", true}, + {"user=auser password='pass word' sslmode='disable dbname=db'", "db", false}, {"", "db", false}, {"dbname=db =disable", "db", false}, } driver := QueryDriver("postgres") - for _, test := range tests { - uri, err := driver.Parse("postgres", test.in) + t.Run(test.in, func(t *testing.T) { + uri, err := driver.Parse("postgres", test.in) - if err != nil && test.valid { - t.Errorf("%q got unexpected error: %s", test.in, err) - } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { - t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) - } + if err != nil && test.valid { + t.Errorf("%q got unexpected error: %s", test.in, err) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) + } + }) } } From eb6d3b1637bdf336a92ecdd18a3fe0aed622b1f8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 28 Jul 2021 11:41:58 +0800 Subject: [PATCH 101/179] Make drone faster (#2008) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2008 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.drone.yml b/.drone.yml index 8a9f8877..869d0114 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,9 +30,15 @@ steps: - make test - make test-sqlite3 - TEST_CACHE_ENABLE=true make test-sqlite3 - - TEST_QUOTE_POLICY=reserved make test-sqlite3 +- name: test-sqlite + image: golang:1.15 + volumes: + - name: cache + path: /go/pkg/mod + depends_on: + - test-vet + commands: - make test-sqlite - - TEST_CACHE_ENABLE=true make test-sqlite - TEST_QUOTE_POLICY=reserved make test-sqlite - name: test-mysql image: golang:1.15 @@ -106,7 +112,6 @@ steps: commands: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - - TEST_QUOTE_POLICY=reserved make test-mysql volumes: - name: cache @@ -144,7 +149,6 @@ steps: TEST_MYSQL_PASSWORD: commands: - make test-mysql - - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql volumes: @@ -237,8 +241,6 @@ steps: TEST_MSSQL_PASSWORD: "yourStrong(!)Password" commands: - make test-mssql - - TEST_CACHE_ENABLE=true make test-mssql - - TEST_QUOTE_POLICY=reserved make test-mssql - TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql volumes: @@ -278,8 +280,6 @@ steps: TEST_TIDB_PASSWORD: commands: - make test-tidb - - TEST_CACHE_ENABLE=true make test-tidb - - TEST_QUOTE_POLICY=reserved make test-tidb volumes: - name: cache @@ -314,7 +314,6 @@ steps: commands: - sleep 10 - make test-cockroach - - TEST_CACHE_ENABLE=true make test-cockroach volumes: - name: cache From 5240459858ceded7bcc89ca13e33caeb2b86162d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 28 Jul 2021 15:15:35 +0800 Subject: [PATCH 102/179] Move assign functions to convert package (#2015) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2015 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert.go | 414 --------------------------------- internal/convert/conversion.go | 376 ++++++++++++++++++++++++++++++ scan.go | 2 +- session.go | 41 +++- session_find.go | 5 +- session_insert.go | 5 +- 6 files changed, 423 insertions(+), 420 deletions(-) delete mode 100644 convert.go diff --git a/convert.go b/convert.go deleted file mode 100644 index c4fc7867..00000000 --- a/convert.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2017 The Xorm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xorm - -import ( - "database/sql" - "database/sql/driver" - "encoding/json" - "errors" - "fmt" - "math/big" - "reflect" - "strconv" - "time" - - "xorm.io/xorm/internal/convert" -) - -var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error - -func strconvErr(err error) error { - if ne, ok := err.(*strconv.NumError); ok { - return ne.Err - } - return err -} - -func cloneBytes(b []byte) []byte { - if b == nil { - return nil - } - c := make([]byte, len(b)) - copy(c, b) - return c -} - -// convertAssign copies to dest the value in src, converting it if possible. -// An error is returned if the copy would result in loss of information. -// dest should be a pointer type. -func convertAssign(dest, src interface{}, originalLocation *time.Location, convertedLocation *time.Location) error { - // Common cases, without reflect. - switch s := src.(type) { - case *interface{}: - return convertAssign(dest, *s, originalLocation, convertedLocation) - case string: - switch d := dest.(type) { - case *string: - if d == nil { - return errNilPtr - } - *d = s - return nil - case *[]byte: - if d == nil { - return errNilPtr - } - *d = []byte(s) - return nil - } - case []byte: - switch d := dest.(type) { - case *string: - if d == nil { - return errNilPtr - } - *d = string(s) - return nil - case *interface{}: - if d == nil { - return errNilPtr - } - *d = cloneBytes(s) - return nil - case *[]byte: - if d == nil { - return errNilPtr - } - *d = cloneBytes(s) - return nil - } - case time.Time: - switch d := dest.(type) { - case *string: - *d = s.Format(time.RFC3339Nano) - return nil - case *[]byte: - if d == nil { - return errNilPtr - } - *d = []byte(s.Format(time.RFC3339Nano)) - return nil - } - case nil: - switch d := dest.(type) { - case *interface{}: - if d == nil { - return errNilPtr - } - *d = nil - return nil - case *[]byte: - if d == nil { - return errNilPtr - } - *d = nil - return nil - } - case *sql.NullString: - switch d := dest.(type) { - case *int: - if s.Valid { - *d, _ = strconv.Atoi(s.String) - } - return nil - case *int64: - if s.Valid { - *d, _ = strconv.ParseInt(s.String, 10, 64) - } - return nil - case *string: - if s.Valid { - *d = s.String - } - return nil - case *time.Time: - if s.Valid { - var err error - dt, err := convert.String2Time(s.String, originalLocation, convertedLocation) - if err != nil { - return err - } - *d = *dt - } - return nil - case *sql.NullTime: - if s.Valid { - var err error - dt, err := convert.String2Time(s.String, originalLocation, convertedLocation) - if err != nil { - return err - } - d.Valid = true - d.Time = *dt - } - return nil - case *big.Float: - if s.Valid { - if d == nil { - d = big.NewFloat(0) - } - d.SetString(s.String) - } - return nil - } - case *sql.NullInt32: - switch d := dest.(type) { - case *int: - if s.Valid { - *d = int(s.Int32) - } - return nil - case *int8: - if s.Valid { - *d = int8(s.Int32) - } - return nil - case *int16: - if s.Valid { - *d = int16(s.Int32) - } - return nil - case *int32: - if s.Valid { - *d = s.Int32 - } - return nil - case *int64: - if s.Valid { - *d = int64(s.Int32) - } - return nil - } - case *sql.NullInt64: - switch d := dest.(type) { - case *int: - if s.Valid { - *d = int(s.Int64) - } - return nil - case *int8: - if s.Valid { - *d = int8(s.Int64) - } - return nil - case *int16: - if s.Valid { - *d = int16(s.Int64) - } - return nil - case *int32: - if s.Valid { - *d = int32(s.Int64) - } - return nil - case *int64: - if s.Valid { - *d = s.Int64 - } - return nil - } - case *sql.NullFloat64: - switch d := dest.(type) { - case *int: - if s.Valid { - *d = int(s.Float64) - } - return nil - case *float64: - if s.Valid { - *d = s.Float64 - } - return nil - } - case *sql.NullBool: - switch d := dest.(type) { - case *bool: - if s.Valid { - *d = s.Bool - } - return nil - } - case *sql.NullTime: - switch d := dest.(type) { - case *time.Time: - if s.Valid { - *d = s.Time - } - return nil - case *string: - if s.Valid { - *d = s.Time.In(convertedLocation).Format("2006-01-02 15:04:05") - } - return nil - } - case *convert.NullUint32: - switch d := dest.(type) { - case *uint8: - if s.Valid { - *d = uint8(s.Uint32) - } - return nil - case *uint16: - if s.Valid { - *d = uint16(s.Uint32) - } - return nil - case *uint: - if s.Valid { - *d = uint(s.Uint32) - } - return nil - } - case *convert.NullUint64: - switch d := dest.(type) { - case *uint64: - if s.Valid { - *d = s.Uint64 - } - return nil - } - case *sql.RawBytes: - switch d := dest.(type) { - case convert.Conversion: - return d.FromDB(*s) - } - } - - var sv reflect.Value - - switch d := dest.(type) { - case *string: - sv = reflect.ValueOf(src) - switch sv.Kind() { - case reflect.Bool, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Float32, reflect.Float64: - *d = convert.AsString(src) - return nil - } - case *[]byte: - if b, ok := convert.AsBytes(src); ok { - *d = b - return nil - } - case *bool: - bv, err := driver.Bool.ConvertValue(src) - if err == nil { - *d = bv.(bool) - } - return err - case *interface{}: - *d = src - return nil - } - - return convertAssignV(reflect.ValueOf(dest), src) -} - -func convertAssignV(dv reflect.Value, src interface{}) error { - if src == nil { - return nil - } - - if dv.Type().Implements(scannerType) { - return dv.Interface().(sql.Scanner).Scan(src) - } - - switch dv.Kind() { - case reflect.Ptr: - if dv.IsNil() { - dv.Set(reflect.New(dv.Type().Elem())) - } - return convertAssignV(dv.Elem(), src) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - i64, err := convert.AsInt64(src) - if err != nil { - err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) - } - dv.SetInt(i64) - return nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - u64, err := convert.AsUint64(src) - if err != nil { - err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) - } - dv.SetUint(u64) - return nil - case reflect.Float32, reflect.Float64: - f64, err := convert.AsFloat64(src) - if err != nil { - err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) - } - dv.SetFloat(f64) - return nil - case reflect.String: - dv.SetString(convert.AsString(src)) - return nil - case reflect.Bool: - b, err := convert.AsBool(src) - if err != nil { - return err - } - dv.SetBool(b) - return nil - case reflect.Slice, reflect.Map, reflect.Struct, reflect.Array: - data, ok := convert.AsBytes(src) - if !ok { - return fmt.Errorf("onvertAssignV: src cannot be as bytes %#v", src) - } - if data == nil { - return nil - } - if dv.Kind() != reflect.Ptr { - dv = dv.Addr() - } - return json.Unmarshal(data, dv.Interface()) - default: - return fmt.Errorf("convertAssignV: unsupported Scan, storing driver.Value type %T into type %T", src, dv.Interface()) - } -} - -func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { - switch tp.Kind() { - case reflect.Ptr: - return asKind(vv.Elem(), tp.Elem()) - case reflect.Int64: - return vv.Int(), nil - case reflect.Int: - return int(vv.Int()), nil - case reflect.Int32: - return int32(vv.Int()), nil - case reflect.Int16: - return int16(vv.Int()), nil - case reflect.Int8: - return int8(vv.Int()), nil - case reflect.Uint64: - return vv.Uint(), nil - case reflect.Uint: - return uint(vv.Uint()), nil - case reflect.Uint32: - return uint32(vv.Uint()), nil - case reflect.Uint16: - return uint16(vv.Uint()), nil - case reflect.Uint8: - return uint8(vv.Uint()), nil - case reflect.String: - return vv.String(), nil - case reflect.Slice: - if tp.Elem().Kind() == reflect.Uint8 { - v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64) - if err != nil { - return nil, err - } - return v, nil - } - } - return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) -} diff --git a/internal/convert/conversion.go b/internal/convert/conversion.go index 16f1a92a..096fcfaf 100644 --- a/internal/convert/conversion.go +++ b/internal/convert/conversion.go @@ -4,9 +4,385 @@ package convert +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "math/big" + "reflect" + "strconv" + "time" +) + // Conversion is an interface. A type implements Conversion will according // the custom method to fill into database and retrieve from database. type Conversion interface { FromDB([]byte) error ToDB() ([]byte, error) } + +// ErrNilPtr represents an error +var ErrNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error + +func strconvErr(err error) error { + if ne, ok := err.(*strconv.NumError); ok { + return ne.Err + } + return err +} + +func cloneBytes(b []byte) []byte { + if b == nil { + return nil + } + c := make([]byte, len(b)) + copy(c, b) + return c +} + +// Assign copies to dest the value in src, converting it if possible. +// An error is returned if the copy would result in loss of information. +// dest should be a pointer type. +func Assign(dest, src interface{}, originalLocation *time.Location, convertedLocation *time.Location) error { + // Common cases, without reflect. + switch s := src.(type) { + case *interface{}: + return Assign(dest, *s, originalLocation, convertedLocation) + case string: + switch d := dest.(type) { + case *string: + if d == nil { + return ErrNilPtr + } + *d = s + return nil + case *[]byte: + if d == nil { + return ErrNilPtr + } + *d = []byte(s) + return nil + } + case []byte: + switch d := dest.(type) { + case *string: + if d == nil { + return ErrNilPtr + } + *d = string(s) + return nil + case *interface{}: + if d == nil { + return ErrNilPtr + } + *d = cloneBytes(s) + return nil + case *[]byte: + if d == nil { + return ErrNilPtr + } + *d = cloneBytes(s) + return nil + } + case time.Time: + switch d := dest.(type) { + case *string: + *d = s.Format(time.RFC3339Nano) + return nil + case *[]byte: + if d == nil { + return ErrNilPtr + } + *d = []byte(s.Format(time.RFC3339Nano)) + return nil + } + case nil: + switch d := dest.(type) { + case *interface{}: + if d == nil { + return ErrNilPtr + } + *d = nil + return nil + case *[]byte: + if d == nil { + return ErrNilPtr + } + *d = nil + return nil + } + case *sql.NullString: + switch d := dest.(type) { + case *int: + if s.Valid { + *d, _ = strconv.Atoi(s.String) + } + return nil + case *int64: + if s.Valid { + *d, _ = strconv.ParseInt(s.String, 10, 64) + } + return nil + case *string: + if s.Valid { + *d = s.String + } + return nil + case *time.Time: + if s.Valid { + var err error + dt, err := String2Time(s.String, originalLocation, convertedLocation) + if err != nil { + return err + } + *d = *dt + } + return nil + case *sql.NullTime: + if s.Valid { + var err error + dt, err := String2Time(s.String, originalLocation, convertedLocation) + if err != nil { + return err + } + d.Valid = true + d.Time = *dt + } + return nil + case *big.Float: + if s.Valid { + if d == nil { + d = big.NewFloat(0) + } + d.SetString(s.String) + } + return nil + } + case *sql.NullInt32: + switch d := dest.(type) { + case *int: + if s.Valid { + *d = int(s.Int32) + } + return nil + case *int8: + if s.Valid { + *d = int8(s.Int32) + } + return nil + case *int16: + if s.Valid { + *d = int16(s.Int32) + } + return nil + case *int32: + if s.Valid { + *d = s.Int32 + } + return nil + case *int64: + if s.Valid { + *d = int64(s.Int32) + } + return nil + } + case *sql.NullInt64: + switch d := dest.(type) { + case *int: + if s.Valid { + *d = int(s.Int64) + } + return nil + case *int8: + if s.Valid { + *d = int8(s.Int64) + } + return nil + case *int16: + if s.Valid { + *d = int16(s.Int64) + } + return nil + case *int32: + if s.Valid { + *d = int32(s.Int64) + } + return nil + case *int64: + if s.Valid { + *d = s.Int64 + } + return nil + } + case *sql.NullFloat64: + switch d := dest.(type) { + case *int: + if s.Valid { + *d = int(s.Float64) + } + return nil + case *float64: + if s.Valid { + *d = s.Float64 + } + return nil + } + case *sql.NullBool: + switch d := dest.(type) { + case *bool: + if s.Valid { + *d = s.Bool + } + return nil + } + case *sql.NullTime: + switch d := dest.(type) { + case *time.Time: + if s.Valid { + *d = s.Time + } + return nil + case *string: + if s.Valid { + *d = s.Time.In(convertedLocation).Format("2006-01-02 15:04:05") + } + return nil + } + case *NullUint32: + switch d := dest.(type) { + case *uint8: + if s.Valid { + *d = uint8(s.Uint32) + } + return nil + case *uint16: + if s.Valid { + *d = uint16(s.Uint32) + } + return nil + case *uint: + if s.Valid { + *d = uint(s.Uint32) + } + return nil + } + case *NullUint64: + switch d := dest.(type) { + case *uint64: + if s.Valid { + *d = s.Uint64 + } + return nil + } + case *sql.RawBytes: + switch d := dest.(type) { + case Conversion: + return d.FromDB(*s) + } + } + + var sv reflect.Value + + switch d := dest.(type) { + case *string: + sv = reflect.ValueOf(src) + switch sv.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + *d = AsString(src) + return nil + } + case *[]byte: + if b, ok := AsBytes(src); ok { + *d = b + return nil + } + case *bool: + bv, err := driver.Bool.ConvertValue(src) + if err == nil { + *d = bv.(bool) + } + return err + case *interface{}: + *d = src + return nil + } + + return AssignValue(reflect.ValueOf(dest), src) +} + +var ( + scannerTypePlaceHolder sql.Scanner + scannerType = reflect.TypeOf(&scannerTypePlaceHolder).Elem() +) + +// AssignValue assign src as dv +func AssignValue(dv reflect.Value, src interface{}) error { + if src == nil { + return nil + } + + if dv.Type().Implements(scannerType) { + return dv.Interface().(sql.Scanner).Scan(src) + } + + switch dv.Kind() { + case reflect.Ptr: + if dv.IsNil() { + dv.Set(reflect.New(dv.Type().Elem())) + } + return AssignValue(dv.Elem(), src) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i64, err := AsInt64(src) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) + } + dv.SetInt(i64) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + u64, err := AsUint64(src) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) + } + dv.SetUint(u64) + return nil + case reflect.Float32, reflect.Float64: + f64, err := AsFloat64(src) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T to a %s: %v", src, dv.Kind(), err) + } + dv.SetFloat(f64) + return nil + case reflect.String: + dv.SetString(AsString(src)) + return nil + case reflect.Bool: + b, err := AsBool(src) + if err != nil { + return err + } + dv.SetBool(b) + return nil + case reflect.Slice, reflect.Map, reflect.Struct, reflect.Array: + data, ok := AsBytes(src) + if !ok { + return fmt.Errorf("convert.AssignValue: src cannot be as bytes %#v", src) + } + if data == nil { + return nil + } + if dv.Kind() != reflect.Ptr { + dv = dv.Addr() + } + return json.Unmarshal(data, dv.Interface()) + default: + return fmt.Errorf("convert.AssignValue: unsupported Scan, storing driver.Value type %T into type %T", src, dv.Interface()) + } +} diff --git a/scan.go b/scan.go index 83ad0b02..b712f18a 100644 --- a/scan.go +++ b/scan.go @@ -235,7 +235,7 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column for i, replaced := range replaces { if replaced { - if err = convertAssign(vv[i], scanResults[i], engine.DatabaseTZ, engine.TZLocation); err != nil { + if err = convert.Assign(vv[i], scanResults[i], engine.DatabaseTZ, engine.TZLocation); err != nil { return err } } diff --git a/session.go b/session.go index a15f5c3c..304d1079 100644 --- a/session.go +++ b/session.go @@ -15,6 +15,7 @@ import ( "hash/crc32" "io" "reflect" + "strconv" "strings" "xorm.io/xorm/contexts" @@ -464,6 +465,44 @@ func (session *Session) setJSON(fieldValue *reflect.Value, fieldType reflect.Typ return nil } +func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { + switch tp.Kind() { + case reflect.Ptr: + return asKind(vv.Elem(), tp.Elem()) + case reflect.Int64: + return vv.Int(), nil + case reflect.Int: + return int(vv.Int()), nil + case reflect.Int32: + return int32(vv.Int()), nil + case reflect.Int16: + return int16(vv.Int()), nil + case reflect.Int8: + return int8(vv.Int()), nil + case reflect.Uint64: + return vv.Uint(), nil + case reflect.Uint: + return uint(vv.Uint()), nil + case reflect.Uint32: + return uint32(vv.Uint()), nil + case reflect.Uint16: + return uint16(vv.Uint()), nil + case reflect.Uint8: + return uint8(vv.Uint()), nil + case reflect.String: + return vv.String(), nil + case reflect.Slice: + if tp.Elem().Kind() == reflect.Uint8 { + v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64) + if err != nil { + return nil, err + } + return v, nil + } + } + return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) +} + func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflect.Value, scanResult interface{}, table *schemas.Table) error { v, ok := scanResult.(*interface{}) @@ -612,7 +651,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } } // switch fieldType.Kind() - return convertAssignV(fieldValue.Addr(), scanResult) + return convert.AssignValue(fieldValue.Addr(), scanResult) } func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { diff --git a/session_find.go b/session_find.go index 2f5438fb..82a302b7 100644 --- a/session_find.go +++ b/session_find.go @@ -10,6 +10,7 @@ import ( "xorm.io/builder" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/statements" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" @@ -280,7 +281,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { cols := table.PKColumns() if len(cols) == 1 { - return convertAssign(dst, pk[0], nil, nil) + return convert.Assign(dst, pk[0], nil, nil) } dst = pk @@ -478,7 +479,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in keyValue := reflect.New(keyType) var ikey interface{} if len(key) == 1 { - if err := convertAssignV(keyValue, key[0]); err != nil { + if err := convert.AssignValue(keyValue, key[0]); err != nil { return err } ikey = keyValue.Elem().Interface() diff --git a/session_insert.go b/session_insert.go index f35cca53..1583858e 100644 --- a/session_insert.go +++ b/session_insert.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -378,7 +379,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 1, nil } - return 1, convertAssignV(*aiValue, id) + return 1, convert.AssignValue(*aiValue, id) } res, err := session.exec(sqlStr, args...) @@ -418,7 +419,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return res.RowsAffected() } - if err := convertAssignV(*aiValue, id); err != nil { + if err := convert.AssignValue(*aiValue, id); err != nil { return 0, err } From 4383669bcdfd90a1169b9a9a3739dac0844d56b5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Jul 2021 11:04:43 +0800 Subject: [PATCH 103/179] Fix DBMetas returned unsigned tinyint (#2017) Fix #2011 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2017 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/mssql.go | 2 +- dialects/mysql.go | 14 +++++++++++- dialects/postgres.go | 4 ++-- dialects/sqlite3.go | 5 +++-- integrations/types_test.go | 39 ++++++++++++++++++++++++++++++++ schemas/type.go | 46 +++++++++++++++++++++----------------- tags/parser.go | 4 ++++ tags/tag.go | 8 +++++++ 8 files changed, 96 insertions(+), 26 deletions(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index 3b935396..0eeb1bcd 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -318,7 +318,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case schemas.MediumInt: + case schemas.MediumInt, schemas.TinyInt, schemas.SmallInt, schemas.UnsignedMediumInt, schemas.UnsignedTinyInt, schemas.UnsignedSmallInt: res = schemas.Int case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: res = db.defaultVarchar + "(MAX)" diff --git a/dialects/mysql.go b/dialects/mysql.go index 0ad68833..21128527 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -263,6 +263,7 @@ func (db *mysql) SetParams(params map[string]string) { func (db *mysql) SQLType(c *schemas.Column) string { var res string + var isUnsigned bool switch t := c.SQLType.Name; t { case schemas.Bool: res = schemas.TinyInt @@ -309,8 +310,19 @@ func (db *mysql) SQLType(c *schemas.Column) string { res = schemas.Text case schemas.UnsignedInt: res = schemas.Int + isUnsigned = true case schemas.UnsignedBigInt: res = schemas.BigInt + isUnsigned = true + case schemas.UnsignedMediumInt: + res = schemas.MediumInt + isUnsigned = true + case schemas.UnsignedSmallInt: + res = schemas.SmallInt + isUnsigned = true + case schemas.UnsignedTinyInt: + res = schemas.TinyInt + isUnsigned = true default: res = t } @@ -329,7 +341,7 @@ func (db *mysql) SQLType(c *schemas.Column) string { res += "(" + strconv.Itoa(c.Length) + ")" } - if c.SQLType.Name == schemas.UnsignedBigInt || c.SQLType.Name == schemas.UnsignedInt { + if isUnsigned { res += " UNSIGNED" } diff --git a/dialects/postgres.go b/dialects/postgres.go index cf760e18..96ebfc85 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -879,13 +879,13 @@ func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case schemas.TinyInt: + case schemas.TinyInt, schemas.UnsignedTinyInt: res = schemas.SmallInt return res case schemas.Bit: res = schemas.Boolean return res - case schemas.MediumInt, schemas.Int, schemas.Integer: + case schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedMediumInt, schemas.UnsignedSmallInt: if c.IsAutoIncrement { return schemas.Serial } diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index dae6bf93..ac17fd92 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -217,8 +217,9 @@ func (db *sqlite3) SQLType(c *schemas.Column) string { case schemas.Char, schemas.Varchar, schemas.NVarchar, schemas.TinyText, schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: return schemas.Text - case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, - schemas.UnsignedBigInt, schemas.UnsignedInt: + case schemas.Bit, schemas.TinyInt, schemas.UnsignedTinyInt, schemas.SmallInt, + schemas.UnsignedSmallInt, schemas.MediumInt, schemas.Int, schemas.UnsignedInt, + schemas.BigInt, schemas.UnsignedBigInt, schemas.Integer: return schemas.Integer case schemas.Float, schemas.Double, schemas.Real: return schemas.Real diff --git a/integrations/types_test.go b/integrations/types_test.go index 9d4e46a0..4bdbb6fe 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -493,6 +493,45 @@ func TestUnsignedUint32(t *testing.T) { assert.EqualValues(t, uint64(math.MaxUint32), v.Id) } +func TestUnsignedTinyInt(t *testing.T) { + type MyUnsignedTinyIntStruct struct { + Id uint8 `xorm:"unsigned tinyint"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(MyUnsignedTinyIntStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 1, len(tables[0].Columns())) + + switch testEngine.Dialect().URI().DBType { + case schemas.SQLITE: + assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) + case schemas.MYSQL: + assert.EqualValues(t, "UNSIGNED TINYINT", tables[0].Columns()[0].SQLType.Name) + case schemas.POSTGRES: + assert.EqualValues(t, "SMALLINT", tables[0].Columns()[0].SQLType.Name) + case schemas.MSSQL: + assert.EqualValues(t, "INT", tables[0].Columns()[0].SQLType.Name) + default: + assert.False(t, true, "Unsigned is not implemented") + } + + cnt, err := testEngine.Insert(&MyUnsignedTinyIntStruct{ + Id: math.MaxUint8, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var v MyUnsignedTinyIntStruct + has, err := testEngine.Get(&v) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, uint64(math.MaxUint32), v.Id) +} + type MyDecimal big.Int func (d *MyDecimal) FromDB(data []byte) error { diff --git a/schemas/type.go b/schemas/type.go index d64251bf..d799db08 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -92,16 +92,19 @@ func (s *SQLType) IsXML() bool { // enumerates all the database column types var ( - Bit = "BIT" - UnsignedBit = "UNSIGNED BIT" - TinyInt = "TINYINT" - SmallInt = "SMALLINT" - MediumInt = "MEDIUMINT" - Int = "INT" - UnsignedInt = "UNSIGNED INT" - Integer = "INTEGER" - BigInt = "BIGINT" - UnsignedBigInt = "UNSIGNED BIGINT" + Bit = "BIT" + UnsignedBit = "UNSIGNED BIT" + TinyInt = "TINYINT" + UnsignedTinyInt = "UNSIGNED TINYINT" + SmallInt = "SMALLINT" + UnsignedSmallInt = "UNSIGNED SMALLINT" + MediumInt = "MEDIUMINT" + UnsignedMediumInt = "UNSIGNED MEDIUMINT" + Int = "INT" + UnsignedInt = "UNSIGNED INT" + Integer = "INTEGER" + BigInt = "BIGINT" + UnsignedBigInt = "UNSIGNED BIGINT" Enum = "ENUM" Set = "SET" @@ -158,16 +161,19 @@ var ( Array = "ARRAY" SqlTypes = map[string]int{ - Bit: NUMERIC_TYPE, - UnsignedBit: NUMERIC_TYPE, - TinyInt: NUMERIC_TYPE, - SmallInt: NUMERIC_TYPE, - MediumInt: NUMERIC_TYPE, - Int: NUMERIC_TYPE, - UnsignedInt: NUMERIC_TYPE, - Integer: NUMERIC_TYPE, - BigInt: NUMERIC_TYPE, - UnsignedBigInt: NUMERIC_TYPE, + Bit: NUMERIC_TYPE, + UnsignedBit: NUMERIC_TYPE, + TinyInt: NUMERIC_TYPE, + UnsignedTinyInt: NUMERIC_TYPE, + SmallInt: NUMERIC_TYPE, + UnsignedSmallInt: NUMERIC_TYPE, + MediumInt: NUMERIC_TYPE, + UnsignedMediumInt: NUMERIC_TYPE, + Int: NUMERIC_TYPE, + UnsignedInt: NUMERIC_TYPE, + Integer: NUMERIC_TYPE, + BigInt: NUMERIC_TYPE, + UnsignedBigInt: NUMERIC_TYPE, Enum: TEXT_TYPE, Set: TEXT_TYPE, diff --git a/tags/parser.go b/tags/parser.go index 7d8c3bd6..e6245a68 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -217,6 +217,10 @@ func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, f if col.SQLType.Name == "" { col.SQLType = schemas.Type2SQLType(field.Type) } + if ctx.isUnsigned && col.SQLType.IsNumeric() && !strings.HasPrefix(col.SQLType.Name, "UNSIGNED") { + col.SQLType.Name = "UNSIGNED " + col.SQLType.Name + } + parser.dialect.SQLType(col) if col.Length == 0 { col.Length = col.SQLType.DefaultLength diff --git a/tags/tag.go b/tags/tag.go index 32cdb37c..4e1f1ce7 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -93,6 +93,7 @@ type Context struct { hasCacheTag bool hasNoCacheTag bool ignoreNext bool + isUnsigned bool } // Handler describes tag handler for XORM @@ -122,6 +123,7 @@ var ( "NOCACHE": NoCacheTagHandler, "COMMENT": CommentTagHandler, "EXTENDS": ExtendsTagHandler, + "UNSIGNED": UnsignedTagHandler, } ) @@ -268,6 +270,12 @@ func UniqueTagHandler(ctx *Context) error { return nil } +// UnsignedTagHandler represents the column is unsigned +func UnsignedTagHandler(ctx *Context) error { + ctx.isUnsigned = true + return nil +} + // CommentTagHandler add comment to column func CommentTagHandler(ctx *Context) error { if len(ctx.params) > 0 { From aeed22016fb9c8abac9f8b049f8b219e66fafa89 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Jul 2021 16:12:09 +0800 Subject: [PATCH 104/179] Support batch insert map (#2019) Fix #1767 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2019 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_insert_test.go | 79 ++++++++++++++ internal/statements/insert.go | 53 ++++++++++ session_insert.go | 159 +++++++++++++++++++--------- 3 files changed, 239 insertions(+), 52 deletions(-) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index ce52d3c4..cd56a958 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -1065,3 +1065,82 @@ func TestInsertDeleted(t *testing.T) { assert.NoError(t, err) assert.True(t, has) } + +func TestInsertMultipleMap(t *testing.T) { + type InsertMultipleMap struct { + Id int64 + Width uint32 + Height uint32 + Name string + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(InsertMultipleMap)) + + cnt, err := testEngine.Table(new(InsertMultipleMap)).Insert([]map[string]interface{}{ + { + "width": 20, + "height": 10, + "name": "lunny", + }, + { + "width": 30, + "height": 20, + "name": "xiaolunwen", + }, + }) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + + var res []InsertMultipleMap + err = testEngine.Find(&res) + assert.NoError(t, err) + assert.EqualValues(t, 2, len(res)) + assert.EqualValues(t, InsertMultipleMap{ + Id: 1, + Width: 20, + Height: 10, + Name: "lunny", + }, res[0]) + assert.EqualValues(t, InsertMultipleMap{ + Id: 2, + Width: 30, + Height: 20, + Name: "xiaolunwen", + }, res[1]) + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(InsertMultipleMap)) + + cnt, err = testEngine.Table(new(InsertMultipleMap)).Insert([]map[string]string{ + { + "width": "20", + "height": "10", + "name": "lunny", + }, + { + "width": "30", + "height": "20", + "name": "xiaolunwen", + }, + }) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + + res = make([]InsertMultipleMap, 0, 2) + err = testEngine.Find(&res) + assert.NoError(t, err) + assert.EqualValues(t, 2, len(res)) + assert.EqualValues(t, InsertMultipleMap{ + Id: 1, + Width: 20, + Height: 10, + Name: "lunny", + }, res[0]) + assert.EqualValues(t, InsertMultipleMap{ + Id: 2, + Width: 30, + Height: 20, + Name: "xiaolunwen", + }, res[1]) +} diff --git a/internal/statements/insert.go b/internal/statements/insert.go index 4e43c5bd..84547cdf 100644 --- a/internal/statements/insert.go +++ b/internal/statements/insert.go @@ -5,6 +5,7 @@ package statements import ( + "errors" "fmt" "strings" @@ -205,3 +206,55 @@ func (statement *Statement) GenInsertMapSQL(columns []string, args []interface{} return buf.String(), buf.Args(), nil } + +func (statement *Statement) GenInsertMultipleMapSQL(columns []string, argss [][]interface{}) (string, []interface{}, error) { + var ( + buf = builder.NewWriter() + exprs = statement.ExprColumns + tableName = statement.TableName() + ) + + if _, err := buf.WriteString(fmt.Sprintf("INSERT INTO %s (", statement.quote(tableName))); err != nil { + return "", nil, err + } + + if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(columns, exprs.ColNames()...), ","); err != nil { + return "", nil, err + } + + // if insert where + if statement.Conds().IsValid() { + return "", nil, errors.New("batch insert don't support with where") + } + + if _, err := buf.WriteString(") VALUES "); err != nil { + return "", nil, err + } + for i, args := range argss { + if _, err := buf.WriteString("("); err != nil { + return "", nil, err + } + if err := statement.WriteArgs(buf, args); err != nil { + return "", nil, err + } + + if len(exprs) > 0 { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + if err := exprs.WriteArgs(buf); err != nil { + return "", nil, err + } + } + if _, err := buf.WriteString(")"); err != nil { + return "", nil, err + } + if i < len(argss)-1 { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + } + } + + return buf.String(), buf.Args(), nil +} diff --git a/session_insert.go b/session_insert.go index 1583858e..b116b9ff 100644 --- a/session_insert.go +++ b/session_insert.go @@ -18,7 +18,7 @@ import ( ) // ErrNoElementsOnSlice represents an error there is no element when insert -var ErrNoElementsOnSlice = errors.New("No element on slice when insert") +var ErrNoElementsOnSlice = errors.New("no element on slice when insert") // Insert insert one or more beans func (session *Session) Insert(beans ...interface{}) (int64, error) { @@ -36,71 +36,42 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) { }() for _, bean := range beans { - switch bean.(type) { + var cnt int64 + var err error + switch v := bean.(type) { case map[string]interface{}: - cnt, err := session.insertMapInterface(bean.(map[string]interface{})) - if err != nil { - return affected, err - } - affected += cnt + cnt, err = session.insertMapInterface(v) case []map[string]interface{}: - s := bean.([]map[string]interface{}) - for i := 0; i < len(s); i++ { - cnt, err := session.insertMapInterface(s[i]) - if err != nil { - return affected, err - } - affected += cnt - } + cnt, err = session.insertMultipleMapInterface(v) case map[string]string: - cnt, err := session.insertMapString(bean.(map[string]string)) - if err != nil { - return affected, err - } - affected += cnt + cnt, err = session.insertMapString(v) case []map[string]string: - s := bean.([]map[string]string) - for i := 0; i < len(s); i++ { - cnt, err := session.insertMapString(s[i]) - if err != nil { - return affected, err - } - affected += cnt - } + cnt, err = session.insertMultipleMapString(v) default: sliceValue := reflect.Indirect(reflect.ValueOf(bean)) if sliceValue.Kind() == reflect.Slice { - size := sliceValue.Len() - if size <= 0 { - return 0, ErrNoElementsOnSlice - } - - cnt, err := session.innerInsertMulti(bean) - if err != nil { - return affected, err - } - affected += cnt + cnt, err = session.insertMultipleStruct(bean) } else { - cnt, err := session.innerInsert(bean) - if err != nil { - return affected, err - } - affected += cnt + cnt, err = session.insertStruct(bean) } } + if err != nil { + return affected, err + } + affected += cnt } return affected, err } -func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error) { +func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, error) { sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) if sliceValue.Kind() != reflect.Slice { return 0, errors.New("needs a pointer to a slice") } if sliceValue.Len() <= 0 { - return 0, errors.New("could not insert a empty slice") + return 0, ErrNoElementsOnSlice } if err := session.statement.SetRefBean(sliceValue.Index(0).Interface()); err != nil { @@ -269,14 +240,10 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { return 0, ErrPtrSliceType } - if sliceValue.Len() <= 0 { - return 0, ErrNoElementsOnSlice - } - - return session.innerInsertMulti(rowsSlicePtr) + return session.insertMultipleStruct(rowsSlicePtr) } -func (session *Session) innerInsert(bean interface{}) (int64, error) { +func (session *Session) insertStruct(bean interface{}) (int64, error) { if err := session.statement.SetRefBean(bean); err != nil { return 0, err } @@ -434,7 +401,7 @@ func (session *Session) InsertOne(bean interface{}) (int64, error) { defer session.Close() } - return session.innerInsert(bean) + return session.insertStruct(bean) } func (session *Session) cacheInsert(table string) error { @@ -561,6 +528,37 @@ func (session *Session) insertMapInterface(m map[string]interface{}) (int64, err return session.insertMap(columns, args) } +func (session *Session) insertMultipleMapInterface(maps []map[string]interface{}) (int64, error) { + if len(maps) <= 0 { + return 0, ErrNoElementsOnSlice + } + + tableName := session.statement.TableName() + if len(tableName) <= 0 { + return 0, ErrTableNotFound + } + + var columns = make([]string, 0, len(maps[0])) + exprs := session.statement.ExprColumns + for k := range maps[0] { + if !exprs.IsColExist(k) { + columns = append(columns, k) + } + } + sort.Strings(columns) + + var argss = make([][]interface{}, 0, len(maps)) + for _, m := range maps { + var args = make([]interface{}, 0, len(m)) + for _, colName := range columns { + args = append(args, m[colName]) + } + argss = append(argss, args) + } + + return session.insertMultipleMap(columns, argss) +} + func (session *Session) insertMapString(m map[string]string) (int64, error) { if len(m) == 0 { return 0, ErrParamsType @@ -589,6 +587,37 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { return session.insertMap(columns, args) } +func (session *Session) insertMultipleMapString(maps []map[string]string) (int64, error) { + if len(maps) <= 0 { + return 0, ErrNoElementsOnSlice + } + + tableName := session.statement.TableName() + if len(tableName) <= 0 { + return 0, ErrTableNotFound + } + + var columns = make([]string, 0, len(maps[0])) + exprs := session.statement.ExprColumns + for k := range maps[0] { + if !exprs.IsColExist(k) { + columns = append(columns, k) + } + } + sort.Strings(columns) + + var argss = make([][]interface{}, 0, len(maps)) + for _, m := range maps { + var args = make([]interface{}, 0, len(m)) + for _, colName := range columns { + args = append(args, m[colName]) + } + argss = append(argss, args) + } + + return session.insertMultipleMap(columns, argss) +} + func (session *Session) insertMap(columns []string, args []interface{}) (int64, error) { tableName := session.statement.TableName() if len(tableName) <= 0 { @@ -614,3 +643,29 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, } return affected, nil } + +func (session *Session) insertMultipleMap(columns []string, argss [][]interface{}) (int64, error) { + tableName := session.statement.TableName() + if len(tableName) <= 0 { + return 0, ErrTableNotFound + } + + sql, args, err := session.statement.GenInsertMultipleMapSQL(columns, argss) + if err != nil { + return 0, err + } + + if err := session.cacheInsert(tableName); err != nil { + return 0, err + } + + res, err := session.exec(sql, args...) + if err != nil { + return 0, err + } + affected, err := res.RowsAffected() + if err != nil { + return 0, err + } + return affected, nil +} From 2afa222871ad0dfa2caccc0614c99ce8b7a41107 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Jul 2021 19:51:10 +0800 Subject: [PATCH 105/179] Fix deleted column (#2014) Fix #2013 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2014 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- tags/parser.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tags/parser.go b/tags/parser.go index e6245a68..5f9fd528 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -300,11 +300,5 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table.AddColumn(col) } // end for - deletedColumn := table.DeletedColumn() - // check columns - if deletedColumn != nil { - deletedColumn.Nullable = true - } - return table, nil } From f22b0cc3697539dbc460c11d99380f16d8d50d8e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 4 Aug 2021 09:20:54 +0800 Subject: [PATCH 106/179] Update changelog for 1.2.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd567b27..f669da8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,40 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.2.0](https://gitea.com/xorm/xorm/releases/tag/1.2.0) - 2021-08-04 + +* BREAKING + * Exec with time arg now will obey time zone settings on engine (#1989) + * Query interface (#1965) + * Support delete with no bean (#1926) + * Nil ptr is nullable (#1919) +* FEATURES + * Support batch insert map (#2019) + * Support big.Float (#1973) +* BUGFIXES + * fix possible null dereference in internal/statements/query.go (#1988) + * Fix bug on dumptable (#1984) +* ENHANCEMENTS + * Move assign functions to convert package (#2015) + * refactor conversion (#2001) + * refactor some code (#2000) + * refactor insert condition generation (#1998) + * refactor and add setjson function (#1997) + * Get struct and Find support big.Float (#1976) + * refactor slice2Bean (#1974, #1975) + * refactor get (#1967) + * Replace #1044 (#1935) + * Support Get time.Time (#1933) +* TESTING + * Add benchmark tests (#1978) + * Add tests for github.com/shopspring/decimal support (#1977) + * Add test for get map with NULL column (#1948) + * Add test for limit with query (#1787) +* MISC + * Fix DBMetas returned unsigned tinyint (#2017) + * Fix deleted column (#2014) + * Add database alias table and fix wrong warning (#1947) + ## [1.1.2](https://gitea.com/xorm/xorm/releases/tag/1.1.2) - 2021-07-04 * BUILD From 42c2f158e80bafffa9488f1d572d8fcea5688ea0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 4 Aug 2021 16:12:10 +0800 Subject: [PATCH 107/179] Fix timesatmp (#2021) Now `Datetime` support `Datime(6)` to indicate the time scale. For different database , the max scale is not different. `Datetime` means `Datetime(0)`. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2021 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/mssql.go | 16 +++++-- dialects/time.go | 73 +++++++++++++++++--------------- engine.go | 10 ++--- integrations/time_test.go | 58 ++++++++++++++++++++++++- internal/convert/time.go | 9 ++++ internal/statements/statement.go | 6 ++- internal/statements/update.go | 5 ++- internal/statements/values.go | 4 +- session_delete.go | 5 ++- session_insert.go | 38 ++++++++--------- session_update.go | 10 ++++- 11 files changed, 165 insertions(+), 69 deletions(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index 0eeb1bcd..1c56e7a4 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -313,8 +313,12 @@ func (db *mssql) SQLType(c *schemas.Column) string { if c.Length == 0 { res += "(MAX)" } - case schemas.TimeStamp: - res = schemas.DateTime + case schemas.TimeStamp, schemas.DateTime: + if c.Length > 3 { + res = "DATETIME2" + } else { + return schemas.DateTime + } case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 @@ -357,7 +361,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { res = t } - if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime { + if res == schemas.Int || res == schemas.Bit { return res } @@ -498,6 +502,12 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName col.Length /= 2 col.Length2 /= 2 } + case "DATETIME2": + col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 7, DefaultLength2: 0} + col.Length = scale + case "DATETIME": + col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 3, DefaultLength2: 0} + col.Length = scale case "IMAGE": col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0} case "NCHAR": diff --git a/dialects/time.go b/dialects/time.go index 5aee0c10..f0bbb765 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -5,50 +5,57 @@ package dialects import ( + "strings" "time" "xorm.io/xorm/schemas" ) -// FormatTime format time as column type -func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{}) { - switch sqlTypeName { - case schemas.Time: - s := t.Format("2006-01-02 15:04:05") // time.RFC3339 - v = s[11:19] - case schemas.Date: - v = t.Format("2006-01-02") - case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. - if dialect.URI().DBType == schemas.ORACLE { - v = t - } else { - v = t.Format("2006-01-02 15:04:05") - } - case schemas.TimeStampz: - if dialect.URI().DBType == schemas.MSSQL { - v = t.Format("2006-01-02T15:04:05.9999999Z07:00") - } else { - v = t.Format(time.RFC3339Nano) - } - case schemas.BigInt, schemas.Int: - v = t.Unix() - default: - v = t - } - return -} - // FormatColumnTime format column time -func FormatColumnTime(dialect Dialect, defaultTimeZone *time.Location, col *schemas.Column, t time.Time) (v interface{}) { +func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.Column, t time.Time) (interface{}, error) { if t.IsZero() { if col.Nullable { - return nil + return nil, nil + } + + if col.SQLType.IsNumeric() { + return 0, nil } - return "" } + var tmZone = dbLocation if col.TimeZone != nil { - return FormatTime(dialect, col.SQLType.Name, t.In(col.TimeZone)) + tmZone = col.TimeZone + } + + t = t.In(tmZone) + + switch col.SQLType.Name { + case schemas.Date: + return t.Format("2006-01-02"), nil + case schemas.Time: + var layout = "15:04:05" + if col.Length > 0 { + layout += "." + strings.Repeat("0", col.Length) + } + return t.Format(layout), nil + case schemas.DateTime, schemas.TimeStamp: + var layout = "2006-01-02 15:04:05" + if col.Length > 0 { + layout += "." + strings.Repeat("0", col.Length) + } + return t.Format(layout), nil + case schemas.Varchar: + return t.Format("2006-01-02 15:04:05"), nil + case schemas.TimeStampz: + if dialect.URI().DBType == schemas.MSSQL { + return t.Format("2006-01-02T15:04:05.9999999Z07:00"), nil + } else { + return t.Format(time.RFC3339Nano), nil + } + case schemas.BigInt, schemas.Int: + return t.Unix(), nil + default: + return t, nil } - return FormatTime(dialect, col.SQLType.Name, t.In(defaultTimeZone)) } diff --git a/engine.go b/engine.go index 20c07e13..133e9553 100644 --- a/engine.go +++ b/engine.go @@ -1226,13 +1226,13 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { } // nowTime return current time -func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) { +func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time, error) { t := time.Now() - var tz = engine.DatabaseTZ - if !col.DisableTimeZone && col.TimeZone != nil { - tz = col.TimeZone + result, err := dialects.FormatColumnTime(engine.dialect, engine.DatabaseTZ, col, t) + if err != nil { + return nil, time.Time{}, err } - return dialects.FormatTime(engine.dialect, col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) + return result, t.In(engine.TZLocation), nil } // GetColumnMapper returns the column name mapper diff --git a/integrations/time_test.go b/integrations/time_test.go index 50fd1847..cd2e879f 100644 --- a/integrations/time_test.go +++ b/integrations/time_test.go @@ -15,8 +15,12 @@ import ( "github.com/stretchr/testify/assert" ) -func formatTime(t time.Time) string { - return t.Format("2006-01-02 15:04:05") +func formatTime(t time.Time, scales ...int) string { + var layout = "2006-01-02 15:04:05" + if len(scales) > 0 && scales[0] > 0 { + layout += "." + strings.Repeat("0", scales[0]) + } + return t.Format(layout) } func TestTimeUserTime(t *testing.T) { @@ -565,3 +569,53 @@ func TestDeletedInt64(t *testing.T) { assert.True(t, has) assert.EqualValues(t, d1, d4) } + +func TestTimestamp(t *testing.T) { + { + assert.NoError(t, PrepareEngine()) + + type TimestampStruct struct { + Id int64 + InsertTime time.Time `xorm:"DATETIME(6)"` + } + + assertSync(t, new(TimestampStruct)) + + var d1 = TimestampStruct{ + InsertTime: time.Now(), + } + cnt, err := testEngine.Insert(&d1) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var d2 TimestampStruct + has, err := testEngine.ID(d1.Id).Get(&d2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, formatTime(d1.InsertTime, 6), formatTime(d2.InsertTime, 6)) + } + + /*{ + assert.NoError(t, PrepareEngine()) + + type TimestampzStruct struct { + Id int64 + InsertTime time.Time `xorm:"TIMESTAMPZ"` + } + + assertSync(t, new(TimestampzStruct)) + + var d3 = TimestampzStruct{ + InsertTime: time.Now(), + } + cnt, err := testEngine.Insert(&d3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var d4 TimestampzStruct + has, err := testEngine.ID(d3.Id).Get(&d4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, formatTime(d3.InsertTime, 6), formatTime(d4.InsertTime, 6)) + }*/ +} diff --git a/internal/convert/time.go b/internal/convert/time.go index ecb30a3f..e53a19cd 100644 --- a/internal/convert/time.go +++ b/internal/convert/time.go @@ -8,6 +8,7 @@ import ( "database/sql" "fmt" "strconv" + "strings" "time" "xorm.io/xorm/internal/utils" @@ -39,6 +40,14 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t } dt = dt.In(convertedLocation) return &dt, nil + } else if len(s) >= 21 && s[19] == '.' { + var layout = "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20) + dt, err := time.ParseInLocation(layout, s, originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil } else { i, err := strconv.ParseInt(s, 10, 64) if err == nil { diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 8e3c083c..adbeb1c2 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -734,7 +734,11 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { return nil, false, nil } - return dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t), true, nil + res, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) + if err != nil { + return nil, false, err + } + return res, true, nil } else if fieldType.ConvertibleTo(schemas.BigFloatType) { t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float) v := t.String() diff --git a/internal/statements/update.go b/internal/statements/update.go index 39a7f829..a8a174f9 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -208,7 +208,10 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } - val = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) + val, err = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) + if err != nil { + return nil, nil, err + } } else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = nulType.Value() if val == nil && !requiredField { diff --git a/internal/statements/values.go b/internal/statements/values.go index ada01755..7351fb79 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -87,8 +87,8 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl case reflect.Struct: if fieldType.ConvertibleTo(schemas.TimeType) { t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) - tf := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) - return tf, nil + tf, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) + return tf, err } else if fieldType.ConvertibleTo(nullFloatType) { t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64) if !t.Valid { diff --git a/session_delete.go b/session_delete.go index baabb558..37b9c1cd 100644 --- a/session_delete.go +++ b/session_delete.go @@ -212,7 +212,10 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { paramsLen := len(condArgs) copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) - val, t := session.engine.nowTime(deletedColumn) + val, t, err := session.engine.nowTime(deletedColumn) + if err != nil { + return 0, err + } condArgs[0] = val var colName = deletedColumn.Name diff --git a/session_insert.go b/session_insert.go index b116b9ff..78f0e555 100644 --- a/session_insert.go +++ b/session_insert.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "xorm.io/xorm/dialects" "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" @@ -137,7 +138,10 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e continue } if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { - val, t := session.engine.nowTime(col) + val, t, err := session.engine.nowTime(col) + if err != nil { + return 0, err + } args = append(args, val) var colName = col.Name @@ -427,29 +431,12 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac if col.MapType == schemas.ONLYFROMDB { continue } - - if col.IsDeleted { - colNames = append(colNames, col.Name) - if !col.Nullable { - if col.SQLType.IsNumeric() { - args = append(args, 0) - } else { - args = append(args, time.Time{}.Format("2006-01-02 15:04:05")) - } - } else { - args = append(args, nil) - } - continue - } - if session.statement.OmitColumnMap.Contain(col.Name) { continue } - if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { continue } - if session.statement.IncrColumns.IsColExist(col.Name) { continue } else if session.statement.DecrColumns.IsColExist(col.Name) { @@ -458,6 +445,16 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac continue } + if col.IsDeleted { + arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, time.Time{}) + if err != nil { + return nil, nil, err + } + args = append(args, arg) + colNames = append(colNames, col.Name) + continue + } + fieldValuePtr, err := col.ValueOf(bean) if err != nil { return nil, nil, err @@ -478,7 +475,10 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ { // if time is non-empty, then set to auto time - val, t := session.engine.nowTime(col) + val, t, err := session.engine.nowTime(col) + if err != nil { + return nil, nil, err + } args = append(args, val) var colName = col.Name diff --git a/session_update.go b/session_update.go index 4f8e6961..7d91346e 100644 --- a/session_update.go +++ b/session_update.go @@ -215,7 +215,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 !session.statement.OmitColumnMap.Contain(table.Updated) { colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?") col := table.UpdatedColumn() - val, t := session.engine.nowTime(col) + val, t, err := session.engine.nowTime(col) + if err != nil { + return 0, err + } if session.engine.dialect.URI().DBType == schemas.ORACLE { args = append(args, t) } else { @@ -521,7 +524,10 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ { // if time is non-empty, then set to auto time - val, t := session.engine.nowTime(col) + val, t, err := session.engine.nowTime(col) + if err != nil { + return nil, nil, err + } args = append(args, val) var colName = col.Name From 0b3cc25a102c6e4a643d117bed24ab42d9495747 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 5 Aug 2021 14:04:11 +0800 Subject: [PATCH 108/179] Add pgx driver support (#1795) Fix #858 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1795 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 37 +++ Makefile | 14 +- dialects/mssql.go | 2 +- engine.go | 1 + go.mod | 2 +- go.sum | 476 ++++++++++++++++++++++++++-- integrations/engine_test.go | 1 + integrations/session_update_test.go | 4 +- schemas/type.go | 132 +++----- tags/parser.go | 32 +- 10 files changed, 576 insertions(+), 125 deletions(-) diff --git a/.drone.yml b/.drone.yml index 869d0114..34b9a514 100644 --- a/.drone.yml +++ b/.drone.yml @@ -205,6 +205,43 @@ steps: commands: - TEST_QUOTE_POLICY=reserved make test-postgres +- name: test-pgx + pull: never + image: golang:1.15 + volumes: + - name: cache + path: /go/pkg/mod + depends_on: + - test-postgres-schema + environment: + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + commands: + - make test-pgx + - TEST_CACHE_ENABLE=true make test-pgx + - TEST_QUOTE_POLICY=reserved make test-pgx + +- name: test-pgx-schema + pull: never + image: golang:1.15 + volumes: + - name: cache + path: /go/pkg/mod + depends_on: + - test-pgx + environment: + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_SCHEMA: xorm + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + commands: + - make test-pgx + - TEST_CACHE_ENABLE=true make test-pgx + - TEST_QUOTE_POLICY=reserved make test-pgx + volumes: - name: cache host: diff --git a/Makefile b/Makefile index 5675589d..e986082e 100644 --- a/Makefile +++ b/Makefile @@ -211,7 +211,19 @@ test-sqlite3-schema: go-check .PHONY: test-sqlite3\#% test-sqlite3\#%: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m + +.PNONY: test-pgx +test-pgx: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=pgx -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m + +.PHONY: test-pgx\#% +test-pgx\#%: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=pgx -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m .PHONY: test-sqlite test-sqlite: go-check diff --git a/dialects/mssql.go b/dialects/mssql.go index 1c56e7a4..2121e71d 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -285,7 +285,7 @@ func (db *mssql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve func (db *mssql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case schemas.Bool: + case schemas.Bool, schemas.Boolean: res = schemas.Bit if strings.EqualFold(c.Default, "true") { c.Default = "1" diff --git a/engine.go b/engine.go index 133e9553..ec066109 100644 --- a/engine.go +++ b/engine.go @@ -471,6 +471,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch destURI := dialects.URI{ DBType: tp[0], DBName: uri.DBName, + Schema: uri.Schema, } dstDialect.Init(&destURI) } diff --git a/go.mod b/go.mod index 1b3baf0c..d645011c 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/denisenkom/go-mssqldb v0.10.0 github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.7.4 + github.com/jackc/pgx/v4 v4.12.0 github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.10.2 github.com/mattn/go-sqlite3 v1.14.8 @@ -13,7 +14,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - gopkg.in/yaml.v2 v2.2.2 // indirect modernc.org/sqlite v1.11.2 xorm.io/builder v0.3.9 ) diff --git a/go.sum b/go.sum index 3d4b72a6..e8024945 100644 --- a/go.sum +++ b/go.sum @@ -1,141 +1,555 @@ +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= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +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/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= -github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +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/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 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 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +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/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= +github.com/jackc/pgconn v1.9.0 h1:gqibKSTJup/ahCsNKyMZAniPuZEfIqfXFc8FOWVYR+Q= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd h1:eDErF6V/JPJON/B7s68BxwHgfmyOntHJQ8IOaz0x4R8= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgtype v1.8.0 h1:iFVCcVhYlw0PulYCVoguRGm0SE9guIcPcccnLzHj8bA= +github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/pgx/v4 v4.12.0 h1:xiP3TdnkwyslWNp77yE5XAPfxAsU9RMFDe0c1SwN8h4= +github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +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/konsorten/go-windows-terminal-sequences v1.0.2/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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +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_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +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/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +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/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +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= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0= -modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA= -modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY= modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8= -modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -143,23 +557,17 @@ modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY= -modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU= modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w= modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A= -modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= -modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs= -modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc= +modernc.org/tcl v1.5.5 h1:N03RwthgTR/l/eQvz3UjfYnvVVj1G2sZqzFGfoD4HE4= modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= -xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE= -xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/integrations/engine_test.go b/integrations/engine_test.go index b5ecb2c2..cd309d6a 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -16,6 +16,7 @@ import ( _ "github.com/denisenkom/go-mssqldb" _ "github.com/go-sql-driver/mysql" + _ "github.com/jackc/pgx/v4/stdlib" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index cc1042b6..bbcc7600 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -349,7 +349,7 @@ func TestUpdate1(t *testing.T) { And("height = ?", user.Height). And("departname = ?", ""). And("detail_id = ?", 0). - And("is_man = ?", 0). + And("is_man = ?", false). Get(&Userinfo{}) assert.NoError(t, err) assert.True(t, has, "cannot insert properly") @@ -825,7 +825,7 @@ func TestNewUpdate(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 0, af) - af, err = testEngine.Table(new(TbUserInfo)).Where("phone=?", 13126564922).Update(&changeUsr) + af, err = testEngine.Table(new(TbUserInfo)).Where("phone=?", "13126564922").Update(&changeUsr) assert.NoError(t, err) assert.EqualValues(t, 0, af) } diff --git a/schemas/type.go b/schemas/type.go index d799db08..cf730134 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -5,9 +5,9 @@ package schemas import ( + "database/sql" "math/big" "reflect" - "sort" "strings" "time" ) @@ -227,90 +227,44 @@ var ( Serial: NUMERIC_TYPE, BigSerial: NUMERIC_TYPE, + "INT8": NUMERIC_TYPE, + Array: ARRAY_TYPE, } - - intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"} - uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"} -) - -// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison -var ( - emptyString string - boolDefault bool - byteDefault byte - complex64Default complex64 - complex128Default complex128 - float32Default float32 - float64Default float64 - int64Default int64 - uint64Default uint64 - int32Default int32 - uint32Default uint32 - int16Default int16 - uint16Default uint16 - int8Default int8 - uint8Default uint8 - intDefault int - uintDefault uint - timeDefault time.Time - bigFloatDefault big.Float ) // enumerates all types var ( - IntType = reflect.TypeOf(intDefault) - Int8Type = reflect.TypeOf(int8Default) - Int16Type = reflect.TypeOf(int16Default) - Int32Type = reflect.TypeOf(int32Default) - Int64Type = reflect.TypeOf(int64Default) + IntType = reflect.TypeOf((*int)(nil)).Elem() + Int8Type = reflect.TypeOf((*int8)(nil)).Elem() + Int16Type = reflect.TypeOf((*int16)(nil)).Elem() + Int32Type = reflect.TypeOf((*int32)(nil)).Elem() + Int64Type = reflect.TypeOf((*int64)(nil)).Elem() - UintType = reflect.TypeOf(uintDefault) - Uint8Type = reflect.TypeOf(uint8Default) - Uint16Type = reflect.TypeOf(uint16Default) - Uint32Type = reflect.TypeOf(uint32Default) - Uint64Type = reflect.TypeOf(uint64Default) + UintType = reflect.TypeOf((*uint)(nil)).Elem() + Uint8Type = reflect.TypeOf((*uint8)(nil)).Elem() + Uint16Type = reflect.TypeOf((*uint16)(nil)).Elem() + Uint32Type = reflect.TypeOf((*uint32)(nil)).Elem() + Uint64Type = reflect.TypeOf((*uint64)(nil)).Elem() - Float32Type = reflect.TypeOf(float32Default) - Float64Type = reflect.TypeOf(float64Default) + Float32Type = reflect.TypeOf((*float32)(nil)).Elem() + Float64Type = reflect.TypeOf((*float64)(nil)).Elem() - Complex64Type = reflect.TypeOf(complex64Default) - Complex128Type = reflect.TypeOf(complex128Default) + Complex64Type = reflect.TypeOf((*complex64)(nil)).Elem() + Complex128Type = reflect.TypeOf((*complex128)(nil)).Elem() - StringType = reflect.TypeOf(emptyString) - BoolType = reflect.TypeOf(boolDefault) - ByteType = reflect.TypeOf(byteDefault) + StringType = reflect.TypeOf((*string)(nil)).Elem() + BoolType = reflect.TypeOf((*bool)(nil)).Elem() + ByteType = reflect.TypeOf((*byte)(nil)).Elem() BytesType = reflect.SliceOf(ByteType) - TimeType = reflect.TypeOf(timeDefault) - BigFloatType = reflect.TypeOf(bigFloatDefault) -) - -// enumerates all types -var ( - PtrIntType = reflect.PtrTo(IntType) - PtrInt8Type = reflect.PtrTo(Int8Type) - PtrInt16Type = reflect.PtrTo(Int16Type) - PtrInt32Type = reflect.PtrTo(Int32Type) - PtrInt64Type = reflect.PtrTo(Int64Type) - - PtrUintType = reflect.PtrTo(UintType) - PtrUint8Type = reflect.PtrTo(Uint8Type) - PtrUint16Type = reflect.PtrTo(Uint16Type) - PtrUint32Type = reflect.PtrTo(Uint32Type) - PtrUint64Type = reflect.PtrTo(Uint64Type) - - PtrFloat32Type = reflect.PtrTo(Float32Type) - PtrFloat64Type = reflect.PtrTo(Float64Type) - - PtrComplex64Type = reflect.PtrTo(Complex64Type) - PtrComplex128Type = reflect.PtrTo(Complex128Type) - - PtrStringType = reflect.PtrTo(StringType) - PtrBoolType = reflect.PtrTo(BoolType) - PtrByteType = reflect.PtrTo(ByteType) - - PtrTimeType = reflect.PtrTo(TimeType) + TimeType = reflect.TypeOf((*time.Time)(nil)).Elem() + BigFloatType = reflect.TypeOf((*big.Float)(nil)).Elem() + NullFloat64Type = reflect.TypeOf((*sql.NullFloat64)(nil)).Elem() + NullStringType = reflect.TypeOf((*sql.NullString)(nil)).Elem() + NullInt32Type = reflect.TypeOf((*sql.NullInt32)(nil)).Elem() + NullInt64Type = reflect.TypeOf((*sql.NullInt64)(nil)).Elem() + NullBoolType = reflect.TypeOf((*sql.NullBool)(nil)).Elem() ) // Type2SQLType generate SQLType acorrding Go's type @@ -331,7 +285,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.Complex64, reflect.Complex128: st = SQLType{Varchar, 64, 0} case reflect.Array, reflect.Slice, reflect.Map: - if t.Elem() == reflect.TypeOf(byteDefault) { + if t.Elem() == ByteType { st = SQLType{Blob, 0, 0} } else { st = SQLType{Text, 0, 0} @@ -343,6 +297,16 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.Struct: if t.ConvertibleTo(TimeType) { st = SQLType{DateTime, 0, 0} + } else if t.ConvertibleTo(NullFloat64Type) { + st = SQLType{Double, 0, 0} + } else if t.ConvertibleTo(NullStringType) { + st = SQLType{Varchar, 255, 0} + } else if t.ConvertibleTo(NullInt32Type) { + st = SQLType{Integer, 0, 0} + } else if t.ConvertibleTo(NullInt64Type) { + st = SQLType{BigInt, 0, 0} + } else if t.ConvertibleTo(NullBoolType) { + st = SQLType{Boolean, 0, 0} } else { // TODO need to handle association struct st = SQLType{Text, 0, 0} @@ -360,25 +324,25 @@ func SQLType2Type(st SQLType) reflect.Type { name := strings.ToUpper(st.Name) switch name { case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial: - return reflect.TypeOf(1) + return IntType case BigInt, BigSerial: - return reflect.TypeOf(int64(1)) + return Int64Type case Float, Real: - return reflect.TypeOf(float32(1)) + return Float32Type case Double: - return reflect.TypeOf(float64(1)) + return Float64Type case Char, NChar, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName: - return reflect.TypeOf("") + return StringType case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier: - return reflect.TypeOf([]byte{}) + return BytesType case Bool: - return reflect.TypeOf(true) + return BoolType case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year: - return reflect.TypeOf(timeDefault) + return TimeType case Decimal, Numeric, Money, SmallMoney: - return reflect.TypeOf("") + return StringType default: - return reflect.TypeOf("") + return StringType } } diff --git a/tags/parser.go b/tags/parser.go index 5f9fd528..989ac7f9 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -7,6 +7,7 @@ package tags import ( "encoding/gob" "errors" + "fmt" "reflect" "strings" "sync" @@ -127,6 +128,25 @@ func addIndex(indexName string, table *schemas.Table, col *schemas.Column, index // ErrIgnoreField represents an error to ignore field var ErrIgnoreField = errors.New("field will be ignored") +func (parser *Parser) getSQLTypeByType(t reflect.Type) (schemas.SQLType, error) { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Struct { + v, ok := parser.tableCache.Load(t) + if ok { + pkCols := v.(*schemas.Table).PKColumns() + if len(pkCols) == 1 { + return pkCols[0].SQLType, nil + } + if len(pkCols) > 1 { + return schemas.SQLType{}, fmt.Errorf("unsupported mulitiple primary key on cascade") + } + } + } + return schemas.Type2SQLType(t), nil +} + func (parser *Parser) parseFieldWithNoTag(fieldIndex int, field reflect.StructField, fieldValue reflect.Value) (*schemas.Column, error) { var sqlType schemas.SQLType if fieldValue.CanAddr() { @@ -137,7 +157,11 @@ func (parser *Parser) parseFieldWithNoTag(fieldIndex int, field reflect.StructFi if _, ok := fieldValue.Interface().(convert.Conversion); ok { sqlType = schemas.SQLType{Name: schemas.Text} } else { - sqlType = schemas.Type2SQLType(field.Type) + var err error + sqlType, err = parser.getSQLTypeByType(field.Type) + if err != nil { + return nil, err + } } col := schemas.NewColumn(parser.columnMapper.Obj2Table(field.Name), field.Name, sqlType, sqlType.DefaultLength, @@ -215,7 +239,11 @@ func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, f } if col.SQLType.Name == "" { - col.SQLType = schemas.Type2SQLType(field.Type) + var err error + col.SQLType, err = parser.getSQLTypeByType(field.Type) + if err != nil { + return nil, err + } } if ctx.isUnsigned && col.SQLType.IsNumeric() && !strings.HasPrefix(col.SQLType.Name, "UNSIGNED") { col.SQLType.Name = "UNSIGNED " + col.SQLType.Name From bd1ca33ea5f5b6ebe964f52da3661a356918f356 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 5 Aug 2021 14:47:25 +0800 Subject: [PATCH 109/179] Update docs for pgx (#2024) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2024 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- README.md | 1 + README_CN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index d7217fa2..2312b78e 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Drivers for Go's sql package which currently support database/sql includes: * [Postgres](https://github.com/postgres/postgres) / [Cockroach](https://github.com/cockroachdb/cockroach) - [github.com/lib/pq](https://github.com/lib/pq) + - [github.com/jackc/pgx](https://github.com/jackc/pgx) * [SQLite](https://sqlite.org) - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) diff --git a/README_CN.md b/README_CN.md index 36930faa..1f3b75e2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -40,6 +40,7 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更: * [Postgres](https://github.com/postgres/postgres) / [Cockroach](https://github.com/cockroachdb/cockroach) - [github.com/lib/pq](https://github.com/lib/pq) + - [github.com/jackc/pgx](https://github.com/jackc/pgx) * [SQLite](https://sqlite.org) - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) From 4499c8c5b56d99b43cc4c7729d8beb549af89f2b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 6 Aug 2021 11:26:22 +0800 Subject: [PATCH 110/179] Fix import file bug (#2025) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2025 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/engine_test.go | 5 +++++ integrations/testdata/import2.sql | 3 +++ session_schema.go | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 integrations/testdata/import2.sql diff --git a/integrations/engine_test.go b/integrations/engine_test.go index cd309d6a..02b35a2c 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -229,6 +229,11 @@ func TestImport(t *testing.T) { _, err := sess.ImportFile("./testdata/import1.sql") assert.NoError(t, err) assert.NoError(t, sess.Commit()) + + assert.NoError(t, sess.Begin()) + _, err = sess.ImportFile("./testdata/import2.sql") + assert.NoError(t, err) + assert.NoError(t, sess.Commit()) } func TestDBVersion(t *testing.T) { diff --git a/integrations/testdata/import2.sql b/integrations/testdata/import2.sql new file mode 100644 index 00000000..469b096b --- /dev/null +++ b/integrations/testdata/import2.sql @@ -0,0 +1,3 @@ +CREATE TABLE IF NOT EXISTS `Core_Goods` (`Id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL, `GoodsSN` VARCHAR(20) NULL COMMENT '商品序列号', `GoodsSort` INT(11) NULL COMMENT '商品排序', `GoodsName` VARCHAR(100) NULL COMMENT '商品名称', `GoodsThumb` VARCHAR(500) NULL, `GoodsUnit` VARCHAR(255) NULL, `PerUnitNum` BIGINT(20) NULL, `GoodsState` TINYINT(4) NULL COMMENT '商品状态', `IsClose` TINYINT(1) DEFAULT 0 NULL COMMENT '关闭下单', `GoodsDesc` VARCHAR(200) NULL COMMENT '商品简介', `GoodsContent` TEXT NULL COMMENT '商品详情', `GoodsImages` TEXT NULL COMMENT '商品图片', `MinOrderNum` INT(11) NULL COMMENT '最少下单数', `MaxOrderNum` INT(11) NULL COMMENT '最大下单数', `CategoryId` INT(11) NULL COMMENT '商品分类', `SupplyPrice` BIGINT(20) NULL COMMENT '供货单价', `StockNum` INT(11) NULL COMMENT '库存数量,小于0不限制', `HandleRemarks` VARCHAR(255) NULL COMMENT '处理备注', `ParamsTemplate` TEXT NULL COMMENT '下单参数模板', `PriceTemplateId` INT(11) NULL COMMENT '加价模板id', `GoodsSnapshotId` INT(11) NULL COMMENT '当前快照id', `SupplierUserId` INT(11) NULL COMMENT '供货商用户id', `CanTui` TINYINT(1) DEFAULT 0 NULL COMMENT '是否可以申请退款', `CanRepeat` TINYINT(1) DEFAULT 1 NULL COMMENT '是否可以申请退款', `GoodsType` TINYINT(4) DEFAULT 1 NULL COMMENT '商品类型', `CreatedAt` DATETIME NULL COMMENT '创建时间', `UpdatedAt` DATETIME NULL COMMENT '修改时间', `LastHandlerAdminUserId` INT(11) NULL COMMENT '最后操作管理员', `GoodsMode` TINYINT(4) DEFAULT 0 NULL COMMENT '商品属性', `SaleTotal` INT(11) NULL COMMENT '总销量', `SaleMonth` INT(11) NULL COMMENT '月销量', `Notice` TEXT NULL COMMENT '商品公告', `AfterLunchOrderState` TINYINT(4) NULL COMMENT '下单后状态', `JoinMode` TINYINT(4) DEFAULT 1 NULL COMMENT '对接模式', `ApiOrderLunchConfig` TEXT NULL COMMENT '提交订单配置', `Version` BIGINT(20) DEFAULT 1 NULL) ENGINE=InnoDB; +INSERT INTO `Core_Goods` (`Id`, `GoodsSN`, `GoodsSort`, `GoodsName`, `GoodsThumb`, `GoodsUnit`, `PerUnitNum`, `GoodsState`, `IsClose`, `GoodsDesc`, `GoodsContent`, `GoodsImages`, `MinOrderNum`, `MaxOrderNum`, `CategoryId`, `SupplyPrice`, `StockNum`, `HandleRemarks`, `ParamsTemplate`, `PriceTemplateId`, `GoodsSnapshotId`, `SupplierUserId`, `CanTui`, `CanRepeat`, `GoodsType`, `CreatedAt`, `UpdatedAt`, `LastHandlerAdminUserId`, `GoodsMode`, `SaleTotal`, `SaleMonth`, `Notice`, `AfterLunchOrderState`, `JoinMode`, `ApiOrderLunchConfig`, `Version`) VALUES (42,'2107290140000432',91,' - ','','',1,2,0,'','--','[]',3,10000,10,5974060,-1,'','',1,0,10001,0,1,1,'2021-07-29 01:40:55','2021-07-30 18:28:59',10000,2,0,0,'',0,0,'{"url":"","method":"","type":"","succContactStr":"","data":null}',35); +INSERT INTO `Core_Goods` (`Id`, `GoodsSN`, `GoodsSort`, `GoodsName`, `GoodsThumb`, `GoodsUnit`, `PerUnitNum`, `GoodsState`, `IsClose`, `GoodsDesc`, `GoodsContent`, `GoodsImages`, `MinOrderNum`, `MaxOrderNum`, `CategoryId`, `SupplyPrice`, `StockNum`, `HandleRemarks`, `ParamsTemplate`, `PriceTemplateId`, `GoodsSnapshotId`, `SupplierUserId`, `CanTui`, `CanRepeat`, `GoodsType`, `CreatedAt`, `UpdatedAt`, `LastHandlerAdminUserId`, `GoodsMode`, `SaleTotal`, `SaleMonth`, `Notice`, `AfterLunchOrderState`, `JoinMode`, `ApiOrderLunchConfig`, `Version`) VALUES (43,'2107290140000433',90,' - ','','',1,2,0,'','','[]',3,10000,10,9064091,-1,'','',1,0,10001,0,1,1,'2021-07-29 01:40:55','2021-07-30 18:28:59',10000,2,0,0,'',0,0,'{"url":"","method":"","type":"","succContactStr":"","data":null}',39); \ No newline at end of file diff --git a/session_schema.go b/session_schema.go index 7cfcb626..2e64350f 100644 --- a/session_schema.go +++ b/session_schema.go @@ -469,7 +469,7 @@ func (session *Session) Import(r io.Reader) ([]sql.Result, error) { startComment = false } } else { - if i > 0 && data[i-1] == '-' && data[i] == '-' { + if !inSingleQuote && i > 0 && data[i-1] == '-' && data[i] == '-' { startComment = true continue } From ad1a386b5ef5b738448ea6a0f6996482103a85aa Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Aug 2021 11:00:11 +0800 Subject: [PATCH 111/179] Fix wrong comment (#2027) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2027 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_find_test.go | 40 +++++++++++++++++++++++++++++++ session.go | 9 +++---- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 80f3b72c..1cbf5e42 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -1045,3 +1045,43 @@ func TestUpdateFind(t *testing.T) { err = session.Where("id = ?", tuf.Id).Find(&tufs) assert.NoError(t, err) } + +func TestFindAnonymousStruct(t *testing.T) { + type FindAnonymousStruct struct { + Id int64 + Name string + Age int + IsMan bool + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(FindAnonymousStruct)) + + cnt, err := testEngine.Insert(&FindAnonymousStruct{ + Name: "xlw", + Age: 42, + IsMan: true, + }) + assert.EqualValues(t, 1, cnt) + assert.NoError(t, err) + + var findRes = make([]struct { + Id int64 + Name string + }, 0) + err = testEngine.Table(new(FindAnonymousStruct)).Find(&findRes) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(findRes)) + assert.EqualValues(t, 1, findRes[0].Id) + assert.EqualValues(t, "xlw", findRes[0].Name) + + findRes = make([]struct { + Id int64 + Name string + }, 0) + err = testEngine.Select("`id`,`name`").Table(new(FindAnonymousStruct)).Find(&findRes) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(findRes)) + assert.EqualValues(t, 1, findRes[0].Id) + assert.EqualValues(t, "xlw", findRes[0].Name) +} diff --git a/session.go b/session.go index 304d1079..339aa73f 100644 --- a/session.go +++ b/session.go @@ -34,7 +34,7 @@ type ErrFieldIsNotExist struct { } func (e ErrFieldIsNotExist) Error() string { - return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) + return fmt.Sprintf("field %s is not exist on table %s", e.FieldName, e.TableName) } // ErrFieldIsNotValid is not valid @@ -677,10 +677,11 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b col, fieldValue, err := session.getField(dataStruct, table, colName, idx) if err != nil { - if _, ok := err.(ErrFieldIsNotValid); !ok { - session.engine.logger.Warnf("%v", err) + if _, ok := err.(ErrFieldIsNotExist); ok { + continue + } else { + return nil, err } - continue } if fieldValue == nil { continue From 3a8ae761c543a33fa62e69b6ffb4936a542c9bca Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Aug 2021 11:01:02 +0800 Subject: [PATCH 112/179] Add changelog for v1.2.1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f669da8e..e6a9b3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.2.1](https://gitea.com/xorm/xorm/releases/tag/1.2.1) - 2021-08-08 + +* FEATURES + * Add pgx driver support (#1795) +* BUGFIXES + * Fix wrong comment (#2027) + * Fix import file bug (#2025) +* ENHANCEMENTS + * Fix timesatmp (#2021) + ## [1.2.0](https://gitea.com/xorm/xorm/releases/tag/1.2.0) - 2021-08-04 * BREAKING From fae164488c3b4058657a0d6c7d73aa3c7973e4a2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Aug 2021 15:35:44 +0800 Subject: [PATCH 113/179] Fix problem on README (#2028) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2028 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- README.md | 2 +- README_CN.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2312b78e..f4bee6b6 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Drivers for Go's sql package which currently support database/sql includes: - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) * Oracle - - [github.com/godror/godror)](https://github.com/godror/godror) (experiment) + - [github.com/godror/godror](https://github.com/godror/godror) (experiment) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) ## Installation diff --git a/README_CN.md b/README_CN.md index 1f3b75e2..500bb1fb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -50,7 +50,7 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更: - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) * Oracle - - [github.com/godror/godror)](https://github.com/godror/godror) (试验性支持) + - [github.com/godror/godror](https://github.com/godror/godror) (试验性支持) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持) ## 安装 @@ -65,7 +65,7 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更: # 快速开始 -* 第一步创建引擎,driverName, dataSourceName和database/sql接口相同 +* 第一步创建引擎,`driverName`, `dataSourceName` 和 `database/sql` 接口相同 ```Go engine, err := xorm.NewEngine(driverName, dataSourceName) @@ -103,7 +103,7 @@ engineGroup, err := xorm.NewEngineGroup(masterEngine, []*Engine{slave1Engine, sl 所有使用 `engine` 都可以简单的用 `engineGroup` 来替换。 -* `Query` 最原始的也支持SQL语句查询,返回的结果类型为 []map[string][]byte。`QueryString` 返回 []map[string]string, `QueryInterface` 返回 `[]map[string]interface{}`. +* `Query` 最原始的也支持SQL语句查询,返回的结果类型为 `[]map[string][]byte`。`QueryString` 返回 `[]map[string]string`, `QueryInterface` 返回 `[]map[string]interface{}`. ```Go results, err := engine.Query("select * from user") From cace6862e297b44505543f91efa73c92c2d636ed Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Aug 2021 23:20:53 +0800 Subject: [PATCH 114/179] Move convert back to xorm.io/xorm/convert (#2030) Since `conversion.Conversion` has been referenced by external package, it should not be moved as internal package. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2030 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- {internal/convert => convert}/bool.go | 0 {internal/convert => convert}/conversion.go | 0 {internal/convert => convert}/float.go | 0 {internal/convert => convert}/int.go | 0 {internal/convert => convert}/interface.go | 0 {internal/convert => convert}/scanner.go | 0 {internal/convert => convert}/string.go | 0 {internal/convert => convert}/time.go | 0 {internal/convert => convert}/time_test.go | 0 integrations/session_get_test.go | 2 +- integrations/types_test.go | 2 +- internal/statements/statement.go | 2 +- internal/statements/update.go | 2 +- internal/statements/values.go | 2 +- scan.go | 2 +- session.go | 2 +- session_find.go | 2 +- session_get.go | 2 +- session_insert.go | 2 +- tags/parser.go | 2 +- 20 files changed, 11 insertions(+), 11 deletions(-) rename {internal/convert => convert}/bool.go (100%) rename {internal/convert => convert}/conversion.go (100%) rename {internal/convert => convert}/float.go (100%) rename {internal/convert => convert}/int.go (100%) rename {internal/convert => convert}/interface.go (100%) rename {internal/convert => convert}/scanner.go (100%) rename {internal/convert => convert}/string.go (100%) rename {internal/convert => convert}/time.go (100%) rename {internal/convert => convert}/time_test.go (100%) diff --git a/internal/convert/bool.go b/convert/bool.go similarity index 100% rename from internal/convert/bool.go rename to convert/bool.go diff --git a/internal/convert/conversion.go b/convert/conversion.go similarity index 100% rename from internal/convert/conversion.go rename to convert/conversion.go diff --git a/internal/convert/float.go b/convert/float.go similarity index 100% rename from internal/convert/float.go rename to convert/float.go diff --git a/internal/convert/int.go b/convert/int.go similarity index 100% rename from internal/convert/int.go rename to convert/int.go diff --git a/internal/convert/interface.go b/convert/interface.go similarity index 100% rename from internal/convert/interface.go rename to convert/interface.go diff --git a/internal/convert/scanner.go b/convert/scanner.go similarity index 100% rename from internal/convert/scanner.go rename to convert/scanner.go diff --git a/internal/convert/string.go b/convert/string.go similarity index 100% rename from internal/convert/string.go rename to convert/string.go diff --git a/internal/convert/time.go b/convert/time.go similarity index 100% rename from internal/convert/time.go rename to convert/time.go diff --git a/internal/convert/time_test.go b/convert/time_test.go similarity index 100% rename from internal/convert/time_test.go rename to convert/time_test.go diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index c0376c70..99ecd462 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -14,7 +14,7 @@ import ( "xorm.io/xorm" "xorm.io/xorm/contexts" - "xorm.io/xorm/internal/convert" + "xorm.io/xorm/convert" "xorm.io/xorm/schemas" "github.com/shopspring/decimal" diff --git a/integrations/types_test.go b/integrations/types_test.go index 4bdbb6fe..b08dfec5 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -13,7 +13,7 @@ import ( "testing" "xorm.io/xorm" - "xorm.io/xorm/internal/convert" + "xorm.io/xorm/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/schemas" diff --git a/internal/statements/statement.go b/internal/statements/statement.go index adbeb1c2..1fcc0bba 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -15,8 +15,8 @@ import ( "xorm.io/builder" "xorm.io/xorm/contexts" + "xorm.io/xorm/convert" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" diff --git a/internal/statements/update.go b/internal/statements/update.go index a8a174f9..40159e0c 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -11,8 +11,8 @@ import ( "reflect" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" diff --git a/internal/statements/values.go b/internal/statements/values.go index 7351fb79..4c1360ed 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -12,8 +12,8 @@ import ( "reflect" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/schemas" ) diff --git a/scan.go b/scan.go index b712f18a..56d3c9d6 100644 --- a/scan.go +++ b/scan.go @@ -11,9 +11,9 @@ import ( "reflect" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/schemas" ) diff --git a/session.go b/session.go index 339aa73f..563dfaf2 100644 --- a/session.go +++ b/session.go @@ -19,8 +19,8 @@ import ( "strings" "xorm.io/xorm/contexts" + "xorm.io/xorm/convert" "xorm.io/xorm/core" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/statements" "xorm.io/xorm/log" diff --git a/session_find.go b/session_find.go index 82a302b7..df3bd85d 100644 --- a/session_find.go +++ b/session_find.go @@ -10,7 +10,7 @@ import ( "xorm.io/builder" "xorm.io/xorm/caches" - "xorm.io/xorm/internal/convert" + "xorm.io/xorm/convert" "xorm.io/xorm/internal/statements" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" diff --git a/session_get.go b/session_get.go index 617ca169..08172524 100644 --- a/session_get.go +++ b/session_get.go @@ -15,8 +15,8 @@ import ( "time" "xorm.io/xorm/caches" + "xorm.io/xorm/convert" "xorm.io/xorm/core" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) diff --git a/session_insert.go b/session_insert.go index 78f0e555..a8f365c7 100644 --- a/session_insert.go +++ b/session_insert.go @@ -12,8 +12,8 @@ import ( "strings" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) diff --git a/tags/parser.go b/tags/parser.go index 989ac7f9..efee11e7 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -15,8 +15,8 @@ import ( "unicode" "xorm.io/xorm/caches" + "xorm.io/xorm/convert" "xorm.io/xorm/dialects" - "xorm.io/xorm/internal/convert" "xorm.io/xorm/names" "xorm.io/xorm/schemas" ) From 289c27ebec863c01d9542322114a144a863f464a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 11 Aug 2021 09:29:27 +0800 Subject: [PATCH 115/179] add changelog for 1.2.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a9b3a5..0f0f93e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.2.2](https://gitea.com/xorm/xorm/releases/tag/1.2.2) - 2021-08-11 + +* MISC + * Move convert back to xorm.io/xorm/convert (#2030) + ## [1.2.1](https://gitea.com/xorm/xorm/releases/tag/1.2.1) - 2021-08-08 * FEATURES From dbc2de380b1614e4ef8f0b7ba3d5618fdfec1cb0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 11 Aug 2021 21:17:23 +0800 Subject: [PATCH 116/179] Refactor find (#2031) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2031 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- convert/conversion.go | 3 +++ internal/utils/new.go | 25 +++++++++++++++++ session_find.go | 62 +++++++++++++++---------------------------- 3 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 internal/utils/new.go diff --git a/convert/conversion.go b/convert/conversion.go index 096fcfaf..78a9fd78 100644 --- a/convert/conversion.go +++ b/convert/conversion.go @@ -325,6 +325,9 @@ func AssignValue(dv reflect.Value, src interface{}) error { if src == nil { return nil } + if v, ok := src.(*interface{}); ok { + return AssignValue(dv, *v) + } if dv.Type().Implements(scannerType) { return dv.Interface().(sql.Scanner).Scan(src) diff --git a/internal/utils/new.go b/internal/utils/new.go new file mode 100644 index 00000000..e3b4eae8 --- /dev/null +++ b/internal/utils/new.go @@ -0,0 +1,25 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package utils + +import "reflect" + +// New creates a value according type +func New(tp reflect.Type, length, cap int) reflect.Value { + switch tp.Kind() { + case reflect.Slice: + slice := reflect.MakeSlice(tp, length, cap) + x := reflect.New(slice.Type()) + x.Elem().Set(slice) + return x + case reflect.Map: + mp := reflect.MakeMapWithSize(tp, cap) + x := reflect.New(mp.Type()) + x.Elem().Set(mp) + return x + default: + return reflect.New(tp) + } +} diff --git a/session_find.go b/session_find.go index df3bd85d..47a3d308 100644 --- a/session_find.go +++ b/session_find.go @@ -161,6 +161,16 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error { + elemType := containerValue.Type().Elem() + var isPointer bool + if elemType.Kind() == reflect.Ptr { + isPointer = true + elemType = elemType.Elem() + } + if elemType.Kind() == reflect.Ptr { + return errors.New("pointer to pointer is not supported") + } + rows, err := session.queryRows(sqlStr, args...) if err != nil { return err @@ -177,31 +187,8 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return err } - var newElemFunc func(fields []string) reflect.Value - elemType := containerValue.Type().Elem() - var isPointer bool - if elemType.Kind() == reflect.Ptr { - isPointer = true - elemType = elemType.Elem() - } - if elemType.Kind() == reflect.Ptr { - return errors.New("pointer to pointer is not supported") - } - - newElemFunc = func(fields []string) reflect.Value { - switch elemType.Kind() { - case reflect.Slice: - slice := reflect.MakeSlice(elemType, len(fields), len(fields)) - x := reflect.New(slice.Type()) - x.Elem().Set(slice) - return x - case reflect.Map: - mp := reflect.MakeMap(elemType) - x := reflect.New(mp.Type()) - x.Elem().Set(mp) - return x - } - return reflect.New(elemType) + var newElemFunc = func(fields []string) reflect.Value { + return utils.New(elemType, len(fields), len(fields)) } var containerValueSetFunc func(*reflect.Value, schemas.PK) error @@ -226,10 +213,15 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error { keyValue := reflect.New(keyType) - err := convertPKToValue(table, keyValue.Interface(), pk) - if err != nil { - return err + cols := table.PKColumns() + if len(cols) == 1 { + if err := convert.AssignValue(keyValue, pk[0]); err != nil { + return err + } + } else { + keyValue.Set(reflect.ValueOf(&pk)) } + if isPointer { containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem().Addr()) } else { @@ -241,8 +233,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect if elemType.Kind() == reflect.Struct { var newValue = newElemFunc(fields) - dataStruct := utils.ReflectValue(newValue.Interface()) - tb, err := session.engine.tagParser.ParseWithCache(dataStruct) + tb, err := session.engine.tagParser.ParseWithCache(newValue) if err != nil { return err } @@ -266,7 +257,6 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect default: err = rows.Scan(bean) } - if err != nil { return err } @@ -278,16 +268,6 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return rows.Err() } -func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { - cols := table.PKColumns() - if len(cols) == 1 { - return convert.Assign(dst, pk[0], nil, nil) - } - - dst = pk - return nil -} - func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr interface{}, args ...interface{}) (err error) { if !session.canCache() || utils.IndexNoCase(sqlStr, "having") != -1 || From 7d458b4fcf3cfce01719223ffdf563f5768257d5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 14 Aug 2021 10:57:47 +0800 Subject: [PATCH 117/179] refactor create table for postgres (#2034) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2034 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dialect.go | 33 +++++++++++++++++++++++++ dialects/mssql.go | 39 ++++++++++++++++-------------- dialects/mysql.go | 57 +++++++++++++++++++++++--------------------- dialects/postgres.go | 35 --------------------------- dialects/sqlite3.go | 35 --------------------------- 5 files changed, 84 insertions(+), 115 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index fc11eac1..b6c0853a 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -103,6 +103,39 @@ func (db *Base) URI() *URI { return db.uri } +// CreateTableSQL implements Dialect +func (db *Base) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { + if tableName == "" { + tableName = table.Name + } + + quoter := db.dialect.Quoter() + var b strings.Builder + b.WriteString("CREATE TABLE IF NOT EXISTS ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" (") + + for i, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + s, _ := ColumnString(db.dialect, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1) + b.WriteString(s) + + if i != len(table.ColumnsSeq())-1 { + b.WriteString(", ") + } + } + + if len(table.PrimaryKeys) > 1 { + b.WriteString(", PRIMARY KEY (") + b.WriteString(quoter.Join(table.PrimaryKeys, ",")) + b.WriteString(")") + } + + b.WriteString(")") + + return []string{b.String()}, false +} + // DropTableSQL returns drop table SQL func (db *Base) DropTableSQL(tableName string) (string, bool) { quote := db.dialect.Quoter().Quote diff --git a/dialects/mssql.go b/dialects/mssql.go index 2121e71d..ab010eb0 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -626,34 +626,37 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { - var sql string if tableName == "" { tableName = table.Name } - sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + tableName + "' ) CREATE TABLE " + quoter := db.dialect.Quoter() + var b strings.Builder + b.WriteString("IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '") + quoter.QuoteTo(&b, tableName) + b.WriteString("' ) CREATE TABLE ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" (") - sql += db.Quoter().Quote(tableName) + " (" - - pkList := table.PrimaryKeys - - for _, colName := range table.ColumnsSeq() { + for i, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) - sql += s - sql = strings.TrimSpace(sql) - sql += ", " + s, _ := ColumnString(db.dialect, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1) + b.WriteString(s) + + if i != len(table.ColumnsSeq())-1 { + b.WriteString(", ") + } } - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += strings.Join(pkList, ",") - sql += " ), " + if len(table.PrimaryKeys) > 1 { + b.WriteString(", PRIMARY KEY (") + b.WriteString(quoter.Join(table.PrimaryKeys, ",")) + b.WriteString(")") } - sql = sql[:len(sql)-2] + ")" - sql += ";" - return []string{sql}, true + b.WriteString(")") + + return []string{b.String()}, true } func (db *mssql) ForUpdateSQL(query string) string { diff --git a/dialects/mysql.go b/dialects/mysql.go index 21128527..0489904a 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -626,42 +626,43 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName } func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { - var sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { tableName = table.Name } - quoter := db.Quoter() + quoter := db.dialect.Quoter() + var b strings.Builder + b.WriteString("CREATE TABLE IF NOT EXISTS ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" (") - sql += quoter.Quote(tableName) - sql += " (" + for i, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + s, _ := ColumnString(db.dialect, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1) + b.WriteString(s) - if len(table.ColumnsSeq()) > 0 { - pkList := table.PrimaryKeys - - for _, colName := range table.ColumnsSeq() { - col := table.GetColumn(colName) - s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) - sql += s - sql = strings.TrimSpace(sql) - if len(col.Comment) > 0 { - sql += " COMMENT '" + col.Comment + "'" - } - sql += ", " + if len(col.Comment) > 0 { + b.WriteString(" COMMENT '") + b.WriteString(col.Comment) + b.WriteString("'") } - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += quoter.Join(pkList, ",") - sql += " ), " + if i != len(table.ColumnsSeq())-1 { + b.WriteString(", ") } - - sql = sql[:len(sql)-2] } - sql += ")" + + if len(table.PrimaryKeys) > 1 { + b.WriteString(", PRIMARY KEY (") + b.WriteString(quoter.Join(table.PrimaryKeys, ",")) + b.WriteString(")") + } + + b.WriteString(")") if table.StoreEngine != "" { - sql += " ENGINE=" + table.StoreEngine + b.WriteString(" ENGINE=") + b.WriteString(table.StoreEngine) } var charset = table.Charset @@ -669,13 +670,15 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]strin charset = db.URI().Charset } if len(charset) != 0 { - sql += " DEFAULT CHARSET " + charset + b.WriteString(" DEFAULT CHARSET ") + b.WriteString(charset) } if db.rowFormat != "" { - sql += " ROW_FORMAT=" + db.rowFormat + b.WriteString(" ROW_FORMAT=") + b.WriteString(db.rowFormat) } - return []string{sql}, true + return []string{b.String()}, true } func (db *mysql) Filters() []Filter { diff --git a/dialects/postgres.go b/dialects/postgres.go index 96ebfc85..6b5a8b2f 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -965,41 +965,6 @@ func (db *postgres) AutoIncrStr() string { return "" } -func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { - var sql string - sql = "CREATE TABLE IF NOT EXISTS " - if tableName == "" { - tableName = table.Name - } - - quoter := db.Quoter() - sql += quoter.Quote(tableName) - sql += " (" - - if len(table.ColumnsSeq()) > 0 { - pkList := table.PrimaryKeys - - for _, colName := range table.ColumnsSeq() { - col := table.GetColumn(colName) - s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) - sql += s - sql = strings.TrimSpace(sql) - sql += ", " - } - - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += quoter.Join(pkList, ",") - sql += " ), " - } - - sql = sql[:len(sql)-2] - } - sql += ")" - - return []string{sql}, true -} - func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { if len(db.getSchema()) == 0 { args := []interface{}{tableName, idxName} diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index ac17fd92..4eba8dad 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -285,41 +285,6 @@ func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName)) } -func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { - var sql string - sql = "CREATE TABLE IF NOT EXISTS " - if tableName == "" { - tableName = table.Name - } - - quoter := db.Quoter() - sql += quoter.Quote(tableName) - sql += " (" - - if len(table.ColumnsSeq()) > 0 { - pkList := table.PrimaryKeys - - for _, colName := range table.ColumnsSeq() { - col := table.GetColumn(colName) - s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) - sql += s - sql = strings.TrimSpace(sql) - sql += ", " - } - - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += quoter.Join(pkList, ",") - sql += " ), " - } - - sql = sql[:len(sql)-2] - } - sql += ")" - - return []string{sql}, true -} - func (db *sqlite3) ForUpdateSQL(query string) string { return query } From 7cd6a74c9fe06f6a2c7424a71ca692d985a0796c Mon Sep 17 00:00:00 2001 From: yedf Date: Sat, 21 Aug 2021 11:30:31 +0800 Subject: [PATCH 118/179] expose sql.Tx (#2036) Co-authored-by: yedongfu Reviewed-on: https://gitea.com/xorm/xorm/pulls/2036 Co-authored-by: yedf Co-committed-by: yedf --- session.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/session.go b/session.go index 563dfaf2..499b7df4 100644 --- a/session.go +++ b/session.go @@ -174,6 +174,11 @@ func (session *Session) Engine() *Engine { return session.engine } +// Tx returns session tx +func (session *Session) Tx() *core.Tx { + return session.tx +} + func (session *Session) getQueryer() core.Queryer { if session.tx != nil { return session.tx From c29b9649a91dedda70d1f69bf9072debf440a993 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Aug 2021 13:46:08 +0800 Subject: [PATCH 119/179] Add dameng support (#2007) driver: https://gitee.com/travelliu/dm docker: https://download.dameng.com/eco/dm8/dm8_docker.tar fix #1837 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2007 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 36 + Makefile | 17 +- convert/conversion.go | 4 +- dialects/dameng.go | 1181 ++++++++++++++++++++++++++ dialects/dialect.go | 72 +- dialects/mssql.go | 10 +- dialects/mysql.go | 11 +- dialects/oracle.go | 10 +- dialects/postgres.go | 6 + dialects/sqlite3.go | 6 + engine.go | 76 +- go.mod | 1 + go.sum | 5 +- integrations/cache_test.go | 10 +- integrations/engine_dm_test.go | 13 + integrations/engine_test.go | 5 +- integrations/performance_test.go | 2 +- integrations/session_cols_test.go | 8 +- integrations/session_cond_test.go | 29 +- integrations/session_count_test.go | 12 +- integrations/session_exist_test.go | 48 +- integrations/session_find_test.go | 104 +-- integrations/session_get_test.go | 85 +- integrations/session_insert_test.go | 35 +- integrations/session_iterate_test.go | 2 +- integrations/session_query_test.go | 59 +- integrations/session_raw_test.go | 10 +- integrations/session_schema_test.go | 5 +- integrations/session_test.go | 2 +- integrations/session_tx_test.go | 14 +- integrations/session_update_test.go | 56 +- integrations/tags_test.go | 8 +- integrations/time_test.go | 8 +- integrations/types_null_test.go | 68 +- integrations/types_test.go | 10 +- internal/statements/insert.go | 47 +- internal/statements/statement.go | 14 +- internal/utils/name.go | 6 + internal/utils/slice.go | 13 +- scan.go | 5 +- schemas/quote_test.go | 4 + schemas/type.go | 5 + session.go | 3 + session_get.go | 3 - session_insert.go | 66 +- session_schema.go | 50 +- session_update.go | 6 +- 47 files changed, 1852 insertions(+), 398 deletions(-) create mode 100644 dialects/dameng.go create mode 100644 integrations/engine_dm_test.go diff --git a/.drone.yml b/.drone.yml index 34b9a514..faaeca17 100644 --- a/.drone.yml +++ b/.drone.yml @@ -363,6 +363,41 @@ services: commands: - /cockroach/cockroach start --insecure +# --- +# kind: pipeline +# name: test-dameng +# depends_on: +# - test-cockroach +# trigger: +# ref: +# - refs/heads/master +# - refs/pull/*/head +# steps: +# - name: test-dameng +# pull: never +# image: golang:1.15 +# volumes: +# - name: cache +# path: /go/pkg/mod +# environment: +# TEST_DAMENG_HOST: "dameng:5236" +# TEST_DAMENG_USERNAME: SYSDBA +# TEST_DAMENG_PASSWORD: SYSDBA +# commands: +# - sleep 30 +# - make test-dameng + +# volumes: +# - name: cache +# host: +# path: /tmp/cache + +# services: +# - name: dameng +# image: lunny/dm:v1.0 +# commands: +# - /bin/bash /startDm.sh + --- kind: pipeline name: merge_coverage @@ -374,6 +409,7 @@ depends_on: - test-mssql - test-tidb - test-cockroach + #- test-dameng trigger: ref: - refs/heads/master diff --git a/Makefile b/Makefile index e986082e..e9bd4129 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,10 @@ TEST_TIDB_DBNAME ?= xorm_test TEST_TIDB_USERNAME ?= root TEST_TIDB_PASSWORD ?= +TEST_DAMENG_HOST ?= dameng:5236 +TEST_DAMENG_USERNAME ?= SYSDBA +TEST_DAMENG_PASSWORD ?= SYSDBA + TEST_CACHE_ENABLE ?= false TEST_QUOTE_POLICY ?= always @@ -240,7 +244,6 @@ test-sqlite\#%: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic - .PNONY: test-tidb test-tidb: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ @@ -253,6 +256,18 @@ test-tidb\#%: go-check -conn_str="$(TEST_TIDB_USERNAME):$(TEST_TIDB_PASSWORD)@tcp($(TEST_TIDB_HOST))/$(TEST_TIDB_DBNAME)" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=tidb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic +.PNONY: test-dameng +test-dameng: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=dm -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + -conn_str="dm://$(TEST_DAMENG_USERNAME):$(TEST_DAMENG_PASSWORD)@$(TEST_DAMENG_HOST)" \ + -coverprofile=dameng.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m + +.PHONY: test-dameng\#% +test-dameng\#%: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=dm -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + -conn_str="dm://$(TEST_DAMENG_USERNAME):$(TEST_DAMENG_PASSWORD)@$(TEST_DAMENG_HOST)" \ + -coverprofile=dameng.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m + .PHONY: vet vet: $(GO) vet $(shell $(GO) list ./...) diff --git a/convert/conversion.go b/convert/conversion.go index 78a9fd78..b69e345c 100644 --- a/convert/conversion.go +++ b/convert/conversion.go @@ -283,11 +283,9 @@ func Assign(dest, src interface{}, originalLocation *time.Location, convertedLoc } } - var sv reflect.Value - switch d := dest.(type) { case *string: - sv = reflect.ValueOf(src) + var sv = reflect.ValueOf(src) switch sv.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, diff --git a/dialects/dameng.go b/dialects/dameng.go new file mode 100644 index 00000000..5ba0cfb5 --- /dev/null +++ b/dialects/dameng.go @@ -0,0 +1,1181 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dialects + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net/url" + "strconv" + "strings" + + "xorm.io/xorm/convert" + "xorm.io/xorm/core" + "xorm.io/xorm/internal/utils" + "xorm.io/xorm/schemas" +) + +func init() { + RegisterDriver("dm", &damengDriver{}) + RegisterDialect(schemas.DAMENG, func() Dialect { + return &dameng{} + }) +} + +var ( + damengReservedWords = map[string]bool{ + "ACCESS": true, + "ACCOUNT": true, + "ACTIVATE": true, + "ADD": true, + "ADMIN": true, + "ADVISE": true, + "AFTER": true, + "ALL": true, + "ALL_ROWS": true, + "ALLOCATE": true, + "ALTER": true, + "ANALYZE": true, + "AND": true, + "ANY": true, + "ARCHIVE": true, + "ARCHIVELOG": true, + "ARRAY": true, + "AS": true, + "ASC": true, + "AT": true, + "AUDIT": true, + "AUTHENTICATED": true, + "AUTHORIZATION": true, + "AUTOEXTEND": true, + "AUTOMATIC": true, + "BACKUP": true, + "BECOME": true, + "BEFORE": true, + "BEGIN": true, + "BETWEEN": true, + "BFILE": true, + "BITMAP": true, + "BLOB": true, + "BLOCK": true, + "BODY": true, + "BY": true, + "CACHE": true, + "CACHE_INSTANCES": true, + "CANCEL": true, + "CASCADE": true, + "CAST": true, + "CFILE": true, + "CHAINED": true, + "CHANGE": true, + "CHAR": true, + "CHAR_CS": true, + "CHARACTER": true, + "CHECK": true, + "CHECKPOINT": true, + "CHOOSE": true, + "CHUNK": true, + "CLEAR": true, + "CLOB": true, + "CLONE": true, + "CLOSE": true, + "CLOSE_CACHED_OPEN_CURSORS": true, + "CLUSTER": true, + "COALESCE": true, + "COLUMN": true, + "COLUMNS": true, + "COMMENT": true, + "COMMIT": true, + "COMMITTED": true, + "COMPATIBILITY": true, + "COMPILE": true, + "COMPLETE": true, + "COMPOSITE_LIMIT": true, + "COMPRESS": true, + "COMPUTE": true, + "CONNECT": true, + "CONNECT_TIME": true, + "CONSTRAINT": true, + "CONSTRAINTS": true, + "CONTENTS": true, + "CONTINUE": true, + "CONTROLFILE": true, + "CONVERT": true, + "COST": true, + "CPU_PER_CALL": true, + "CPU_PER_SESSION": true, + "CREATE": true, + "CURRENT": true, + "CURRENT_SCHEMA": true, + "CURREN_USER": true, + "CURSOR": true, + "CYCLE": true, + "DANGLING": true, + "DATABASE": true, + "DATAFILE": true, + "DATAFILES": true, + "DATAOBJNO": true, + "DATE": true, + "DBA": true, + "DBHIGH": true, + "DBLOW": true, + "DBMAC": true, + "DEALLOCATE": true, + "DEBUG": true, + "DEC": true, + "DECIMAL": true, + "DECLARE": true, + "DEFAULT": true, + "DEFERRABLE": true, + "DEFERRED": true, + "DEGREE": true, + "DELETE": true, + "DEREF": true, + "DESC": true, + "DIRECTORY": true, + "DISABLE": true, + "DISCONNECT": true, + "DISMOUNT": true, + "DISTINCT": true, + "DISTRIBUTED": true, + "DML": true, + "DOUBLE": true, + "DROP": true, + "DUMP": true, + "EACH": true, + "ELSE": true, + "ENABLE": true, + "END": true, + "ENFORCE": true, + "ENTRY": true, + "ESCAPE": true, + "EXCEPT": true, + "EXCEPTIONS": true, + "EXCHANGE": true, + "EXCLUDING": true, + "EXCLUSIVE": true, + "EXECUTE": true, + "EXISTS": true, + "EXPIRE": true, + "EXPLAIN": true, + "EXTENT": true, + "EXTENTS": true, + "EXTERNALLY": true, + "FAILED_LOGIN_ATTEMPTS": true, + "FALSE": true, + "FAST": true, + "FILE": true, + "FIRST_ROWS": true, + "FLAGGER": true, + "FLOAT": true, + "FLOB": true, + "FLUSH": true, + "FOR": true, + "FORCE": true, + "FOREIGN": true, + "FREELIST": true, + "FREELISTS": true, + "FROM": true, + "FULL": true, + "FUNCTION": true, + "GLOBAL": true, + "GLOBALLY": true, + "GLOBAL_NAME": true, + "GRANT": true, + "GROUP": true, + "GROUPS": true, + "HASH": true, + "HASHKEYS": true, + "HAVING": true, + "HEADER": true, + "HEAP": true, + "IDENTIFIED": true, + "IDGENERATORS": true, + "IDLE_TIME": true, + "IF": true, + "IMMEDIATE": true, + "IN": true, + "INCLUDING": true, + "INCREMENT": true, + "INDEX": true, + "INDEXED": true, + "INDEXES": true, + "INDICATOR": true, + "IND_PARTITION": true, + "INITIAL": true, + "INITIALLY": true, + "INITRANS": true, + "INSERT": true, + "INSTANCE": true, + "INSTANCES": true, + "INSTEAD": true, + "INT": true, + "INTEGER": true, + "INTERMEDIATE": true, + "INTERSECT": true, + "INTO": true, + "IS": true, + "ISOLATION": true, + "ISOLATION_LEVEL": true, + "KEEP": true, + "KEY": true, + "KILL": true, + "LABEL": true, + "LAYER": true, + "LESS": true, + "LEVEL": true, + "LIBRARY": true, + "LIKE": true, + "LIMIT": true, + "LINK": true, + "LIST": true, + "LOB": true, + "LOCAL": true, + "LOCK": true, + "LOCKED": true, + "LOG": true, + "LOGFILE": true, + "LOGGING": true, + "LOGICAL_READS_PER_CALL": true, + "LOGICAL_READS_PER_SESSION": true, + "LONG": true, + "MANAGE": true, + "MASTER": true, + "MAX": true, + "MAXARCHLOGS": true, + "MAXDATAFILES": true, + "MAXEXTENTS": true, + "MAXINSTANCES": true, + "MAXLOGFILES": true, + "MAXLOGHISTORY": true, + "MAXLOGMEMBERS": true, + "MAXSIZE": true, + "MAXTRANS": true, + "MAXVALUE": true, + "MIN": true, + "MEMBER": true, + "MINIMUM": true, + "MINEXTENTS": true, + "MINUS": true, + "MINVALUE": true, + "MLSLABEL": true, + "MLS_LABEL_FORMAT": true, + "MODE": true, + "MODIFY": true, + "MOUNT": true, + "MOVE": true, + "MTS_DISPATCHERS": true, + "MULTISET": true, + "NATIONAL": true, + "NCHAR": true, + "NCHAR_CS": true, + "NCLOB": true, + "NEEDED": true, + "NESTED": true, + "NETWORK": true, + "NEW": true, + "NEXT": true, + "NOARCHIVELOG": true, + "NOAUDIT": true, + "NOCACHE": true, + "NOCOMPRESS": true, + "NOCYCLE": true, + "NOFORCE": true, + "NOLOGGING": true, + "NOMAXVALUE": true, + "NOMINVALUE": true, + "NONE": true, + "NOORDER": true, + "NOOVERRIDE": true, + "NOPARALLEL": true, + "NOREVERSE": true, + "NORMAL": true, + "NOSORT": true, + "NOT": true, + "NOTHING": true, + "NOWAIT": true, + "NULL": true, + "NUMBER": true, + "NUMERIC": true, + "NVARCHAR2": true, + "OBJECT": true, + "OBJNO": true, + "OBJNO_REUSE": true, + "OF": true, + "OFF": true, + "OFFLINE": true, + "OID": true, + "OIDINDEX": true, + "OLD": true, + "ON": true, + "ONLINE": true, + "ONLY": true, + "OPCODE": true, + "OPEN": true, + "OPTIMAL": true, + "OPTIMIZER_GOAL": true, + "OPTION": true, + "OR": true, + "ORDER": true, + "ORGANIZATION": true, + "OSLABEL": true, + "OVERFLOW": true, + "OWN": true, + "PACKAGE": true, + "PARALLEL": true, + "PARTITION": true, + "PASSWORD": true, + "PASSWORD_GRACE_TIME": true, + "PASSWORD_LIFE_TIME": true, + "PASSWORD_LOCK_TIME": true, + "PASSWORD_REUSE_MAX": true, + "PASSWORD_REUSE_TIME": true, + "PASSWORD_VERIFY_FUNCTION": true, + "PCTFREE": true, + "PCTINCREASE": true, + "PCTTHRESHOLD": true, + "PCTUSED": true, + "PCTVERSION": true, + "PERCENT": true, + "PERMANENT": true, + "PLAN": true, + "PLSQL_DEBUG": true, + "POST_TRANSACTION": true, + "PRECISION": true, + "PRESERVE": true, + "PRIMARY": true, + "PRIOR": true, + "PRIVATE": true, + "PRIVATE_SGA": true, + "PRIVILEGE": true, + "PRIVILEGES": true, + "PROCEDURE": true, + "PROFILE": true, + "PUBLIC": true, + "PURGE": true, + "QUEUE": true, + "QUOTA": true, + "RANGE": true, + "RAW": true, + "RBA": true, + "READ": true, + "READUP": true, + "REAL": true, + "REBUILD": true, + "RECOVER": true, + "RECOVERABLE": true, + "RECOVERY": true, + "REF": true, + "REFERENCES": true, + "REFERENCING": true, + "REFRESH": true, + "RENAME": true, + "REPLACE": true, + "RESET": true, + "RESETLOGS": true, + "RESIZE": true, + "RESOURCE": true, + "RESTRICTED": true, + "RETURN": true, + "RETURNING": true, + "REUSE": true, + "REVERSE": true, + "REVOKE": true, + "ROLE": true, + "ROLES": true, + "ROLLBACK": true, + "ROW": true, + "ROWID": true, + "ROWNUM": true, + "ROWS": true, + "RULE": true, + "SAMPLE": true, + "SAVEPOINT": true, + "SB4": true, + "SCAN_INSTANCES": true, + "SCHEMA": true, + "SCN": true, + "SCOPE": true, + "SD_ALL": true, + "SD_INHIBIT": true, + "SD_SHOW": true, + "SEGMENT": true, + "SEG_BLOCK": true, + "SEG_FILE": true, + "SELECT": true, + "SEQUENCE": true, + "SERIALIZABLE": true, + "SESSION": true, + "SESSION_CACHED_CURSORS": true, + "SESSIONS_PER_USER": true, + "SET": true, + "SHARE": true, + "SHARED": true, + "SHARED_POOL": true, + "SHRINK": true, + "SIZE": true, + "SKIP": true, + "SKIP_UNUSABLE_INDEXES": true, + "SMALLINT": true, + "SNAPSHOT": true, + "SOME": true, + "SORT": true, + "SPECIFICATION": true, + "SPLIT": true, + "SQL_TRACE": true, + "STANDBY": true, + "START": true, + "STATEMENT_ID": true, + "STATISTICS": true, + "STOP": true, + "STORAGE": true, + "STORE": true, + "STRUCTURE": true, + "SUCCESSFUL": true, + "SWITCH": true, + "SYS_OP_ENFORCE_NOT_NULL$": true, + "SYS_OP_NTCIMG$": true, + "SYNONYM": true, + "SYSDATE": true, + "SYSDBA": true, + "SYSOPER": true, + "SYSTEM": true, + "TABLE": true, + "TABLES": true, + "TABLESPACE": true, + "TABLESPACE_NO": true, + "TABNO": true, + "TEMPORARY": true, + "THAN": true, + "THE": true, + "THEN": true, + "THREAD": true, + "TIMESTAMP": true, + "TIME": true, + "TO": true, + "TOPLEVEL": true, + "TRACE": true, + "TRACING": true, + "TRANSACTION": true, + "TRANSITIONAL": true, + "TRIGGER": true, + "TRIGGERS": true, + "TRUE": true, + "TRUNCATE": true, + "TX": true, + "TYPE": true, + "UB2": true, + "UBA": true, + "UID": true, + "UNARCHIVED": true, + "UNDO": true, + "UNION": true, + "UNIQUE": true, + "UNLIMITED": true, + "UNLOCK": true, + "UNRECOVERABLE": true, + "UNTIL": true, + "UNUSABLE": true, + "UNUSED": true, + "UPDATABLE": true, + "UPDATE": true, + "USAGE": true, + "USE": true, + "USER": true, + "USING": true, + "VALIDATE": true, + "VALIDATION": true, + "VALUE": true, + "VALUES": true, + "VARCHAR": true, + "VARCHAR2": true, + "VARYING": true, + "VIEW": true, + "WHEN": true, + "WHENEVER": true, + "WHERE": true, + "WITH": true, + "WITHOUT": true, + "WORK": true, + "WRITE": true, + "WRITEDOWN": true, + "WRITEUP": true, + "XID": true, + "YEAR": true, + "ZONE": true, + } + + damengQuoter = schemas.Quoter{ + Prefix: '"', + Suffix: '"', + IsReserved: schemas.AlwaysReserve, + } +) + +type dameng struct { + Base +} + +func (db *dameng) Init(uri *URI) error { + db.quoter = damengQuoter + return db.Base.Init(db, uri) +} + +func (db *dameng) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "SELECT * FROM V$VERSION") // select id_code + if err != nil { + return nil, err + } + defer rows.Close() + + var version string + if !rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } + return nil, errors.New("unknow version") + } + + if err := rows.Scan(&version); err != nil { + return nil, err + } + return &schemas.Version{ + Number: version, + }, nil +} + +func (db *dameng) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: SequenceAutoincrMode, + } +} + +// DropIndexSQL returns a SQL to drop index +func (db *dameng) DropIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quoter().Quote + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name + } + return fmt.Sprintf("DROP INDEX %v", quote(name)) +} + +func (db *dameng) SQLType(c *schemas.Column) string { + var res string + switch t := c.SQLType.Name; t { + case schemas.TinyInt, "BYTE": + return "TINYINT" + case schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedTinyInt: + return "INTEGER" + case schemas.BigInt, + schemas.UnsignedBigInt, schemas.UnsignedBit, schemas.UnsignedInt, + schemas.Serial, schemas.BigSerial: + return "BIGINT" + case schemas.Bit, schemas.Bool, schemas.Boolean: + return schemas.Bit + case schemas.Uuid: + res = schemas.Varchar + c.Length = 40 + case schemas.Binary: + if c.Length == 0 { + return schemas.Binary + "(MAX)" + } + case schemas.VarBinary, schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea: + return schemas.VarBinary + case schemas.Date: + return schemas.Date + case schemas.Time: + if c.Length > 0 { + return fmt.Sprintf("%s(%d)", schemas.Time, c.Length) + } + return schemas.Time + case schemas.DateTime, schemas.TimeStamp: + res = schemas.TimeStamp + case schemas.TimeStampz: + if c.Length > 0 { + return fmt.Sprintf("TIMESTAMP(%d) WITH TIME ZONE", c.Length) + } + return "TIMESTAMP WITH TIME ZONE" + case schemas.Float: + res = "FLOAT" + case schemas.Real, schemas.Double: + res = "REAL" + case schemas.Numeric, schemas.Decimal, "NUMBER": + res = "NUMERIC" + case schemas.Text, schemas.Json: + return "TEXT" + case schemas.MediumText, schemas.LongText: + res = "CLOB" + case schemas.Char, schemas.Varchar, schemas.TinyText: + res = "VARCHAR2" + default: + res = t + } + + hasLen1 := (c.Length > 0) + hasLen2 := (c.Length2 > 0) + + if hasLen2 { + res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + } else if hasLen1 { + res += "(" + strconv.Itoa(c.Length) + ")" + } + return res +} + +func (db *dameng) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATE": + return schemas.TIME_TYPE + case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": + return schemas.TEXT_TYPE + case "NUMBER": + return schemas.NUMERIC_TYPE + case "BLOB": + return schemas.BLOB_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + +func (db *dameng) AutoIncrStr() string { + return "IDENTITY" +} + +func (db *dameng) IsReserved(name string) bool { + _, ok := damengReservedWords[strings.ToUpper(name)] + return ok +} + +func (db *dameng) DropTableSQL(tableName string) (string, bool) { + return fmt.Sprintf("DROP TABLE %s", db.quoter.Quote(tableName)), false +} + +// ModifyColumnSQL returns a SQL to modify SQL +func (db *dameng) ModifyColumnSQL(tableName string, col *schemas.Column) string { + s, _ := ColumnString(db.dialect, col, false) + return fmt.Sprintf("ALTER TABLE %s MODIFY %s", db.quoter.Quote(tableName), s) +} + +func (db *dameng) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { + if tableName == "" { + tableName = table.Name + } + + quoter := db.Quoter() + var b strings.Builder + b.WriteString("CREATE TABLE ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" (") + + pkList := table.PrimaryKeys + + for i, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.SQLType.IsBool() && !col.DefaultIsEmpty { + if col.Default == "true" { + col.Default = "1" + } else if col.Default == "false" { + col.Default = "0" + } + } + + s, _ := ColumnString(db, col, false) + b.WriteString(s) + if i != len(table.ColumnsSeq())-1 { + b.WriteString(", ") + } + } + + if len(pkList) > 0 { + if len(table.ColumnsSeq()) > 0 { + b.WriteString(", ") + } + b.WriteString(fmt.Sprintf("CONSTRAINT PK_%s PRIMARY KEY (", tableName)) + quoter.JoinWrite(&b, pkList, ",") + b.WriteString(")") + } + b.WriteString(")") + + return b.String(), false, nil +} + +func (db *dameng) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + var q = damengQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + var q = damengQuoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = damengQuoter + } +} + +func (db *dameng) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { + args := []interface{}{tableName, idxName} + return `SELECT INDEX_NAME FROM USER_INDEXES ` + + `WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args +} + +func (db *dameng) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { + return db.HasRecords(queryer, ctx, `SELECT table_name FROM user_tables WHERE table_name = ?`, tableName) +} + +func (db *dameng) IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error) { + var cnt int + rows, err := queryer.QueryContext(ctx, "SELECT COUNT(*) FROM user_sequences WHERE sequence_name = ?", seqName) + if err != nil { + return false, err + } + defer rows.Close() + if !rows.Next() { + if rows.Err() != nil { + return false, rows.Err() + } + return false, errors.New("query sequence failed") + } + + if err := rows.Scan(&cnt); err != nil { + return false, err + } + return cnt > 0, nil +} + +func (db *dameng) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { + args := []interface{}{tableName, colName} + query := "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" + + " AND column_name = ?" + return db.HasRecords(queryer, ctx, query, args...) +} + +var _ sql.Scanner = &dmClobScanner{} + +type dmClobScanner struct { + valid bool + data string +} + +type dmClobObject interface { + GetLength() (int64, error) + ReadString(int, int) (string, error) +} + +//var _ dmClobObject = &dm.DmClob{} + +func (d *dmClobScanner) Scan(data interface{}) error { + if data == nil { + return nil + } + + switch t := data.(type) { + case dmClobObject: // *dm.DmClob + if t == nil { + return nil + } + l, err := t.GetLength() + if err != nil { + return err + } + if l == 0 { + d.valid = true + return nil + } + d.data, err = t.ReadString(1, int(l)) + if err != nil { + return err + } + d.valid = true + return nil + case []byte: + if t == nil { + return nil + } + d.data = string(t) + d.valid = true + return nil + default: + return fmt.Errorf("cannot convert %T as dmClobScanner", data) + } +} + +func addSingleQuote(name string) string { + if len(name) < 2 { + return name + } + if name[0] == '\'' && name[len(name)-1] == '\'' { + return name + } + return fmt.Sprintf("'%s'", name) +} + +func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { + s := `select column_name from user_cons_columns + where constraint_name = (select constraint_name from user_constraints + where table_name = ? and constraint_type ='P')` + rows, err := queryer.QueryContext(ctx, s, tableName) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + var pkNames []string + for rows.Next() { + var pkName string + err = rows.Scan(&pkName) + if err != nil { + return nil, nil, err + } + pkNames = append(pkNames, pkName) + } + if rows.Err() != nil { + return nil, nil, rows.Err() + } + rows.Close() + + s = `SELECT USER_TAB_COLS.COLUMN_NAME, USER_TAB_COLS.DATA_DEFAULT, USER_TAB_COLS.DATA_TYPE, USER_TAB_COLS.DATA_LENGTH, + USER_TAB_COLS.data_precision, USER_TAB_COLS.data_scale, USER_TAB_COLS.NULLABLE, + user_col_comments.comments + FROM USER_TAB_COLS + LEFT JOIN user_col_comments on user_col_comments.TABLE_NAME=USER_TAB_COLS.TABLE_NAME + AND user_col_comments.COLUMN_NAME=USER_TAB_COLS.COLUMN_NAME + WHERE USER_TAB_COLS.table_name = ?` + rows, err = queryer.QueryContext(ctx, s, tableName) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + cols := make(map[string]*schemas.Column) + colSeq := make([]string, 0) + for rows.Next() { + col := new(schemas.Column) + col.Indexes = make(map[string]int) + + var colDefault dmClobScanner + var colName, nullable, dataType, dataPrecision, comment sql.NullString + var dataScale, dataLen sql.NullInt64 + + err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision, + &dataScale, &nullable, &comment) + if err != nil { + return nil, nil, err + } + + if !colName.Valid { + return nil, nil, errors.New("column name is nil") + } + + col.Name = strings.Trim(colName.String, `" `) + if colDefault.valid { + col.Default = colDefault.data + } else { + col.DefaultIsEmpty = true + } + + if nullable.String == "Y" { + col.Nullable = true + } else { + col.Nullable = false + } + + if !comment.Valid { + col.Comment = comment.String + } + if utils.IndexSlice(pkNames, col.Name) > -1 { + col.IsPrimaryKey = true + has, err := db.HasRecords(queryer, ctx, "SELECT * FROM USER_SEQUENCES WHERE SEQUENCE_NAME = ?", utils.SeqName(tableName)) + if err != nil { + return nil, nil, err + } + if has { + col.IsAutoIncrement = true + } + } + + var ( + ignore bool + dt string + len1, len2 int + ) + + dts := strings.Split(dataType.String, "(") + dt = dts[0] + if len(dts) > 1 { + lens := strings.Split(dts[1][:len(dts[1])-1], ",") + if len(lens) > 1 { + len1, _ = strconv.Atoi(lens[0]) + len2, _ = strconv.Atoi(lens[1]) + } else { + len1, _ = strconv.Atoi(lens[0]) + } + } + + switch dt { + case "VARCHAR2": + col.SQLType = schemas.SQLType{Name: "VARCHAR2", DefaultLength: len1, DefaultLength2: len2} + case "VARCHAR": + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: len1, DefaultLength2: len2} + case "TIMESTAMP WITH TIME ZONE": + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + case "NUMBER": + col.SQLType = schemas.SQLType{Name: "NUMBER", DefaultLength: len1, DefaultLength2: len2} + case "LONG", "LONG RAW", "NCLOB", "CLOB", "TEXT": + col.SQLType = schemas.SQLType{Name: schemas.Text, DefaultLength: 0, DefaultLength2: 0} + case "RAW": + col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: 0, DefaultLength2: 0} + case "ROWID": + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 18, DefaultLength2: 0} + case "AQ$_SUBSCRIBERS": + ignore = true + default: + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} + } + + if ignore { + continue + } + + if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, fmt.Errorf("unknown colType %v %v", dataType.String, col.SQLType) + } + + if col.SQLType.Name == "TIMESTAMP" { + col.Length = int(dataScale.Int64) + } else { + col.Length = int(dataLen.Int64) + } + + if col.SQLType.IsTime() { + if !col.DefaultIsEmpty && !strings.EqualFold(col.Default, "CURRENT_TIMESTAMP") { + col.Default = addSingleQuote(col.Default) + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + if rows.Err() != nil { + return nil, nil, rows.Err() + } + + return colSeq, cols, nil +} + +func (db *dameng) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { + s := "SELECT table_name FROM user_tables WHERE temporary = 'N' AND table_name NOT LIKE ?" + args := []interface{}{strings.ToUpper(db.uri.User), "%$%"} + + rows, err := queryer.QueryContext(ctx, s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + tables := make([]*schemas.Table, 0) + for rows.Next() { + table := schemas.NewEmptyTable() + err = rows.Scan(&table.Name) + if err != nil { + return nil, err + } + + tables = append(tables, table) + } + if rows.Err() != nil { + return nil, rows.Err() + } + return tables, nil +} + +func (db *dameng) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { + args := []interface{}{tableName, tableName} + s := "SELECT t.column_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " + + "WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =?" + + " AND t.index_name not in (SELECT index_name FROM ALL_CONSTRAINTS WHERE CONSTRAINT_TYPE='P' AND table_name = ?)" + + rows, err := queryer.QueryContext(ctx, s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + indexes := make(map[string]*schemas.Index) + for rows.Next() { + var indexType int + var indexName, colName, uniqueness string + + err = rows.Scan(&colName, &uniqueness, &indexName) + if err != nil { + return nil, err + } + + indexName = strings.Trim(indexName, `" `) + + var isRegular bool + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + indexName = indexName[5+len(tableName):] + isRegular = true + } + + if uniqueness == "UNIQUE" { + indexType = schemas.UniqueType + } else { + indexType = schemas.IndexType + } + + var index *schemas.Index + var ok bool + if index, ok = indexes[indexName]; !ok { + index = new(schemas.Index) + index.Type = indexType + index.Name = indexName + index.IsRegular = isRegular + indexes[indexName] = index + } + index.AddColumn(colName) + } + if rows.Err() != nil { + return nil, rows.Err() + } + return indexes, nil +} + +func (db *dameng) Filters() []Filter { + return []Filter{} +} + +type damengDriver struct { + baseDriver +} + +// Features return features +func (d *damengDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: false, + } +} + +// Parse parse the datasource +// dm://userName:password@ip:port +func (d *damengDriver) Parse(driverName, dataSourceName string) (*URI, error) { + u, err := url.Parse(dataSourceName) + if err != nil { + return nil, err + } + + if u.User == nil { + return nil, errors.New("user/password needed") + } + + passwd, _ := u.User.Password() + return &URI{ + DBType: schemas.DAMENG, + Proto: u.Scheme, + Host: u.Hostname(), + Port: u.Port(), + DBName: u.User.Username(), + User: u.User.Username(), + Passwd: passwd, + }, nil +} + +func (d *damengDriver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": + var s sql.NullString + return &s, nil + case "NUMBER": + var s sql.NullString + return &s, nil + case "BIGINT": + var s sql.NullInt64 + return &s, nil + case "INTEGER": + var s sql.NullInt32 + return &s, nil + case "DATE", "TIMESTAMP": + var s sql.NullString + return &s, nil + case "BLOB": + var r sql.RawBytes + return &r, nil + case "FLOAT": + var s sql.NullFloat64 + return &s, nil + default: + var r sql.RawBytes + return &r, nil + } +} + +func (d *damengDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, vv ...interface{}) error { + var scanResults = make([]interface{}, 0, len(types)) + var replaces = make([]bool, 0, len(types)) + var err error + for i, v := range vv { + var replaced bool + var scanResult interface{} + switch types[i].DatabaseTypeName() { + case "CLOB", "TEXT": + scanResult = &dmClobScanner{} + replaced = true + case "TIMESTAMP": + scanResult = &sql.NullString{} + replaced = true + default: + scanResult = v + } + + scanResults = append(scanResults, scanResult) + replaces = append(replaces, replaced) + } + + if err = rows.Scan(scanResults...); err != nil { + return err + } + + for i, replaced := range replaces { + if replaced { + switch t := scanResults[i].(type) { + case *dmClobScanner: + var d interface{} + if t.valid { + d = t.data + } else { + d = nil + } + if err := convert.Assign(vv[i], d, ctx.DBLocation, ctx.UserLocation); err != nil { + return err + } + default: + switch types[i].DatabaseTypeName() { + case "TIMESTAMP": + ns := t.(*sql.NullString) + if !ns.Valid { + return nil + } + s := ns.String + fields := strings.Split(s, "+") + if err := convert.Assign(vv[i], strings.Replace(fields[0], "T", " ", -1), ctx.DBLocation, ctx.UserLocation); err != nil { + return err + } + default: + return fmt.Errorf("don't support convert %T to %T", t, vv[i]) + } + } + } + } + + return nil +} diff --git a/dialects/dialect.go b/dialects/dialect.go index b6c0853a..460ab56a 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -38,11 +38,21 @@ func (uri *URI) SetSchema(schema string) { } } +const ( + IncrAutoincrMode = iota + SequenceAutoincrMode +) + +type DialectFeatures struct { + AutoincrMode int // 0 autoincrement column, 1 sequence +} + // Dialect represents a kind of database type Dialect interface { Init(*URI) error URI() *URI Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) + Features() *DialectFeatures SQLType(*schemas.Column) string Alias(string) string // return what a sql type's alias of @@ -61,9 +71,13 @@ type Dialect interface { GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) - CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) + CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) DropTableSQL(tableName string) (string, bool) + CreateSequenceSQL(ctx context.Context, queryer core.Queryer, seqName string) (string, error) + IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error) + DropSequenceSQL(seqName string) (string, error) + GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName string, colName string) (bool, error) AddColumnSQL(tableName string, col *schemas.Column) string @@ -104,7 +118,7 @@ func (db *Base) URI() *URI { } // CreateTableSQL implements Dialect -func (db *Base) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { +func (db *Base) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { if tableName == "" { tableName = table.Name } @@ -133,7 +147,25 @@ func (db *Base) CreateTableSQL(table *schemas.Table, tableName string) ([]string b.WriteString(")") - return []string{b.String()}, false + return b.String(), false, nil +} + +func (db *Base) CreateSequenceSQL(ctx context.Context, queryer core.Queryer, seqName string) (string, error) { + return fmt.Sprintf(`CREATE SEQUENCE %s + minvalue 1 + nomaxvalue + start with 1 + increment by 1 + nocycle + nocache`, seqName), nil +} + +func (db *Base) IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error) { + return false, fmt.Errorf("unsupported sequence feature") +} + +func (db *Base) DropSequenceSQL(seqName string) (string, error) { + return fmt.Sprintf("DROP SEQUENCE %s", seqName), nil } // DropTableSQL returns drop table SQL @@ -285,43 +317,41 @@ func ColumnString(dialect Dialect, col *schemas.Column, includePrimaryKey bool) return "", err } - if err := bd.WriteByte(' '); err != nil { - return "", err - } - if includePrimaryKey && col.IsPrimaryKey { - if _, err := bd.WriteString("PRIMARY KEY "); err != nil { + if _, err := bd.WriteString(" PRIMARY KEY"); err != nil { return "", err } - if col.IsAutoIncrement { - if _, err := bd.WriteString(dialect.AutoIncrStr()); err != nil { - return "", err - } if err := bd.WriteByte(' '); err != nil { return "", err } + if _, err := bd.WriteString(dialect.AutoIncrStr()); err != nil { + return "", err + } } } - if col.Default != "" { - if _, err := bd.WriteString("DEFAULT "); err != nil { + if !col.DefaultIsEmpty { + if _, err := bd.WriteString(" DEFAULT "); err != nil { return "", err } - if _, err := bd.WriteString(col.Default); err != nil { - return "", err - } - if err := bd.WriteByte(' '); err != nil { - return "", err + if col.Default == "" { + if _, err := bd.WriteString("''"); err != nil { + return "", err + } + } else { + if _, err := bd.WriteString(col.Default); err != nil { + return "", err + } } } if col.Nullable { - if _, err := bd.WriteString("NULL "); err != nil { + if _, err := bd.WriteString(" NULL"); err != nil { return "", err } } else { - if _, err := bd.WriteString("NOT NULL "); err != nil { + if _, err := bd.WriteString(" NOT NULL"); err != nil { return "", err } } diff --git a/dialects/mssql.go b/dialects/mssql.go index ab010eb0..cd19afb9 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -282,6 +282,12 @@ func (db *mssql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve }, nil } +func (db *mssql) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: IncrAutoincrMode, + } +} + func (db *mssql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -625,7 +631,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? return indexes, nil } -func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { +func (db *mssql) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { if tableName == "" { tableName = table.Name } @@ -656,7 +662,7 @@ func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) ([]strin b.WriteString(")") - return []string{b.String()}, true + return b.String(), true, nil } func (db *mssql) ForUpdateSQL(query string) string { diff --git a/dialects/mysql.go b/dialects/mysql.go index 0489904a..ce3bd705 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -244,6 +244,12 @@ func (db *mysql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve }, nil } +func (db *mysql) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: IncrAutoincrMode, + } +} + func (db *mysql) SetParams(params map[string]string) { rowFormat, ok := params["rowFormat"] if ok { @@ -625,7 +631,7 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName return indexes, nil } -func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { +func (db *mysql) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { if tableName == "" { tableName = table.Name } @@ -678,7 +684,8 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]strin b.WriteString(" ROW_FORMAT=") b.WriteString(db.rowFormat) } - return []string{b.String()}, true + + return b.String(), true, nil } func (db *mysql) Filters() []Filter { diff --git a/dialects/oracle.go b/dialects/oracle.go index 11a6653b..04652bd6 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -539,6 +539,12 @@ func (db *oracle) Version(ctx context.Context, queryer core.Queryer) (*schemas.V }, nil } +func (db *oracle) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: SequenceAutoincrMode, + } +} + func (db *oracle) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -599,7 +605,7 @@ func (db *oracle) DropTableSQL(tableName string) (string, bool) { return fmt.Sprintf("DROP TABLE `%s`", tableName), false } -func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { +func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { var sql = "CREATE TABLE " if tableName == "" { tableName = table.Name @@ -629,7 +635,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) ([]stri } sql = sql[:len(sql)-2] + ")" - return []string{sql}, false + return sql, false, nil } func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { diff --git a/dialects/postgres.go b/dialects/postgres.go index 6b5a8b2f..822d3a70 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -941,6 +941,12 @@ func (db *postgres) SQLType(c *schemas.Column) string { return res } +func (db *postgres) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: IncrAutoincrMode, + } +} + func (db *postgres) ColumnTypeKind(t string) int { switch strings.ToUpper(t) { case "DATETIME", "TIMESTAMP": diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 4eba8dad..4ff9a39e 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -184,6 +184,12 @@ func (db *sqlite3) Version(ctx context.Context, queryer core.Queryer) (*schemas. }, nil } +func (db *sqlite3) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: IncrAutoincrMode, + } +} + func (db *sqlite3) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: diff --git a/engine.go b/engine.go index ec066109..3681ff3c 100644 --- a/engine.go +++ b/engine.go @@ -248,11 +248,6 @@ func (engine *Engine) SQLType(c *schemas.Column) string { return engine.dialect.SQLType(c) } -// AutoIncrStr Database's autoincrement statement -func (engine *Engine) AutoIncrStr() string { - return engine.dialect.AutoIncrStr() -} - // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. func (engine *Engine) SetConnMaxLifetime(d time.Duration) { engine.DB().SetConnMaxLifetime(d) @@ -441,7 +436,7 @@ func (engine *Engine) DumpTablesToFile(tables []*schemas.Table, fp string, tp .. // DumpTables dump specify tables to io.Writer func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { - return engine.dumpTables(tables, w, tp...) + return engine.dumpTables(context.Background(), tables, w, tp...) } func formatBool(s string, dstDialect dialects.Dialect) string { @@ -457,7 +452,7 @@ func formatBool(s string, dstDialect dialects.Dialect) string { } // dumpTables dump database all table structs and data to w with specify db type -func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { +func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { var dstDialect dialects.Dialect if len(tp) == 0 { dstDialect = engine.dialect @@ -494,9 +489,12 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } } - dstTableName := dstTable.Name + var dstTableName = dstTable.Name + var quoter = dstDialect.Quoter().Quote + var quotedDstTableName = quoter(dstTable.Name) if dstDialect.URI().Schema != "" { dstTableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, dstTable.Name) + quotedDstTableName = fmt.Sprintf("%s.%s", quoter(dstDialect.URI().Schema), quoter(dstTable.Name)) } originalTableName := table.Name if engine.dialect.URI().Schema != "" { @@ -509,13 +507,26 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } } - sqls, _ := dstDialect.CreateTableSQL(dstTable, dstTableName) - for _, s := range sqls { - _, err = io.WriteString(w, s+";\n") + if dstTable.AutoIncrement != "" && dstDialect.Features().AutoincrMode == dialects.SequenceAutoincrMode { + sqlstr, err := dstDialect.CreateSequenceSQL(ctx, engine.db, utils.SeqName(dstTableName)) + if err != nil { + return err + } + _, err = io.WriteString(w, sqlstr+";\n") if err != nil { return err } } + + sqlstr, _, err := dstDialect.CreateTableSQL(ctx, engine.db, dstTable, dstTableName) + if err != nil { + return err + } + _, err = io.WriteString(w, sqlstr+";\n") + if err != nil { + return err + } + if len(dstTable.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL { fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", dstTable.Name) } @@ -552,7 +563,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch sess := engine.NewSession() defer sess.Close() for rows.Next() { - _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") + _, err = io.WriteString(w, "INSERT INTO "+quotedDstTableName+" ("+destColNames+") VALUES (") if err != nil { return err } @@ -563,36 +574,27 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } for i, scanResult := range scanResults { stp := schemas.SQLType{Name: types[i].DatabaseTypeName()} - if stp.IsNumeric() { - s := scanResult.(*sql.NullString) - if s.Valid { - if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil { - return err - } - } else { - if _, err = io.WriteString(w, "NULL"); err != nil { - return err - } - } - } else if stp.IsBool() { - s := scanResult.(*sql.NullString) - if s.Valid { - if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil { - return err - } - } else { - if _, err = io.WriteString(w, "NULL"); err != nil { - return err - } + s := scanResult.(*sql.NullString) + if !s.Valid { + if _, err = io.WriteString(w, "NULL"); err != nil { + return err } } else { - s := scanResult.(*sql.NullString) - if s.Valid { - if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { + if stp.IsBool() || (dstDialect.URI().DBType == schemas.MSSQL && strings.EqualFold(stp.Name, schemas.Bit)) { + if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil { + return err + } + } else if stp.IsNumeric() { + if _, err = io.WriteString(w, s.String); err != nil { + return err + } + } else if sess.engine.dialect.URI().DBType == schemas.DAMENG && stp.IsTime() && len(s.String) == 25 { + r := strings.Replace(s.String[:19], "T", " ", -1) + if _, err = io.WriteString(w, "'"+r+"'"); err != nil { return err } } else { - if _, err = io.WriteString(w, "NULL"); err != nil { + if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { return err } } diff --git a/go.mod b/go.mod index d645011c..98b5617c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module xorm.io/xorm go 1.13 require ( + gitee.com/travelliu/dm v1.8.11192 github.com/denisenkom/go-mssqldb v0.10.0 github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.7.4 diff --git a/go.sum b/go.sum index e8024945..4596f326 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= +gitee.com/travelliu/dm v1.8.11192 h1:aqJT0xhodZjRutIfEXxKYv0CxqmHUHzsbz6SFaRL6OY= +gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= @@ -95,8 +97,9 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= diff --git a/integrations/cache_test.go b/integrations/cache_test.go index 44e817b1..6fdaa5cb 100644 --- a/integrations/cache_test.go +++ b/integrations/cache_test.go @@ -62,7 +62,8 @@ func TestCacheFind(t *testing.T) { } boxes = make([]MailBox, 0, 2) - assert.NoError(t, testEngine.Alias("a").Where("a.id > -1").Asc("a.id").Find(&boxes)) + assert.NoError(t, testEngine.Alias("a").Where("`a`.`id`> -1"). + Asc("`a`.`id`").Find(&boxes)) assert.EqualValues(t, 2, len(boxes)) for i, box := range boxes { assert.Equal(t, inserts[i].Id, box.Id) @@ -77,7 +78,8 @@ func TestCacheFind(t *testing.T) { } boxes2 := make([]MailBox4, 0, 2) - assert.NoError(t, testEngine.Table("mail_box").Where("mail_box.id > -1").Asc("mail_box.id").Find(&boxes2)) + assert.NoError(t, testEngine.Table("mail_box").Where("`mail_box`.`id` > -1"). + Asc("mail_box.id").Find(&boxes2)) assert.EqualValues(t, 2, len(boxes2)) for i, box := range boxes2 { assert.Equal(t, inserts[i].Id, box.Id) @@ -164,14 +166,14 @@ func TestCacheGet(t *testing.T) { assert.NoError(t, err) var box1 MailBox3 - has, err := testEngine.Where("id = ?", inserts[0].Id).Get(&box1) + has, err := testEngine.Where("`id` = ?", inserts[0].Id).Get(&box1) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, "user1", box1.Username) assert.EqualValues(t, "pass1", box1.Password) var box2 MailBox3 - has, err = testEngine.Where("id = ?", inserts[0].Id).Get(&box2) + has, err = testEngine.Where("`id` = ?", inserts[0].Id).Get(&box2) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, "user1", box2.Username) diff --git a/integrations/engine_dm_test.go b/integrations/engine_dm_test.go new file mode 100644 index 00000000..6c2f6103 --- /dev/null +++ b/integrations/engine_dm_test.go @@ -0,0 +1,13 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build dm + +package integrations + +import "xorm.io/xorm/schemas" + +func init() { + dbtypes = append(dbtypes, schemas.DAMENG) +} diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 02b35a2c..f45d6859 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -14,6 +14,7 @@ import ( "xorm.io/xorm" "xorm.io/xorm/schemas" + _ "gitee.com/travelliu/dm" _ "github.com/denisenkom/go-mssqldb" _ "github.com/go-sql-driver/mysql" _ "github.com/jackc/pgx/v4/stdlib" @@ -134,6 +135,8 @@ func TestDump(t *testing.T) { } } +var dbtypes = []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} + func TestDumpTables(t *testing.T) { assert.NoError(t, PrepareEngine()) @@ -169,7 +172,7 @@ func TestDumpTables(t *testing.T) { assert.NoError(t, err) assert.NoError(t, sess.Commit()) - for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} { + for _, tp := range dbtypes { name := fmt.Sprintf("dump_%v-table.sql", tp) t.Run(name, func(t *testing.T) { assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, name, tp)) diff --git a/integrations/performance_test.go b/integrations/performance_test.go index 4b54b40c..241dc7b9 100644 --- a/integrations/performance_test.go +++ b/integrations/performance_test.go @@ -32,7 +32,7 @@ func BenchmarkGetVars(b *testing.B) { b.StartTimer() var myname string for i := 0; i < b.N; i++ { - has, err := testEngine.Cols("name").Table("benchmark_get_vars").Where("id=?", v.Id).Get(&myname) + has, err := testEngine.Cols("name").Table("benchmark_get_vars").Where("`id`=?", v.Id).Get(&myname) b.StopTimer() myname = "" assert.True(b, has) diff --git a/integrations/session_cols_test.go b/integrations/session_cols_test.go index b74c6f8a..18ba4001 100644 --- a/integrations/session_cols_test.go +++ b/integrations/session_cols_test.go @@ -45,7 +45,7 @@ func TestSetExpr(t *testing.T) { assert.EqualValues(t, 1, cnt) var not = "NOT" - if testEngine.Dialect().URI().DBType == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL || testEngine.Dialect().URI().DBType == schemas.DAMENG { not = "~" } cnt, err = testEngine.SetExpr("show", not+" `show`").ID(1).Update(new(UserExpr)) @@ -54,9 +54,9 @@ func TestSetExpr(t *testing.T) { tableName := testEngine.TableName(new(UserExprIssue), true) cnt, err = testEngine.SetExpr("issue_id", - builder.Select("id"). - From(tableName). - Where(builder.Eq{"id": issue.Id})). + builder.Select("`id`"). + From(testEngine.Quote(tableName)). + Where(builder.Eq{"`id`": issue.Id})). ID(1). Update(new(UserExpr)) assert.NoError(t, err) diff --git a/integrations/session_cond_test.go b/integrations/session_cond_test.go index a0a91cad..72a4caf5 100644 --- a/integrations/session_cond_test.go +++ b/integrations/session_cond_test.go @@ -37,49 +37,50 @@ func TestBuilder(t *testing.T) { assert.NoError(t, err) var cond Condition - has, err := testEngine.Where(builder.Eq{"col_name": "col1"}).Get(&cond) + var q = testEngine.Quote + has, err := testEngine.Where(builder.Eq{q("col_name"): "col1"}).Get(&cond) assert.NoError(t, err) assert.Equal(t, true, has, "records should exist") - has, err = testEngine.Where(builder.Eq{"col_name": "col1"}. - And(builder.Eq{"op": OpEqual})). + has, err = testEngine.Where(builder.Eq{q("col_name"): "col1"}. + And(builder.Eq{q("op"): OpEqual})). NoAutoCondition(). Get(&cond) assert.NoError(t, err) assert.Equal(t, true, has, "records should exist") - has, err = testEngine.Where(builder.Eq{"col_name": "col1", "op": OpEqual, "value": "1"}). + has, err = testEngine.Where(builder.Eq{q("col_name"): "col1", q("op"): OpEqual, q("value"): "1"}). NoAutoCondition(). Get(&cond) assert.NoError(t, err) assert.Equal(t, true, has, "records should exist") - has, err = testEngine.Where(builder.Eq{"col_name": "col1"}. - And(builder.Neq{"op": OpEqual})). + has, err = testEngine.Where(builder.Eq{q("col_name"): "col1"}. + And(builder.Neq{q("op"): OpEqual})). NoAutoCondition(). Get(&cond) assert.NoError(t, err) assert.Equal(t, false, has, "records should not exist") var conds []Condition - err = testEngine.Where(builder.Eq{"col_name": "col1"}. - And(builder.Eq{"op": OpEqual})). + err = testEngine.Where(builder.Eq{q("col_name"): "col1"}. + And(builder.Eq{q("op"): OpEqual})). Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") conds = make([]Condition, 0) - err = testEngine.Where(builder.Like{"col_name", "col"}).Find(&conds) + err = testEngine.Where(builder.Like{q("col_name"), "col"}).Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") conds = make([]Condition, 0) - err = testEngine.Where(builder.Expr("col_name = ?", "col1")).Find(&conds) + err = testEngine.Where(builder.Expr(q("col_name")+" = ?", "col1")).Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") conds = make([]Condition, 0) - err = testEngine.Where(builder.In("col_name", "col1", "col2")).Find(&conds) + err = testEngine.Where(builder.In(q("col_name"), "col1", "col2")).Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") @@ -91,8 +92,8 @@ func TestBuilder(t *testing.T) { // complex condtions var where = builder.NewCond() if true { - where = where.And(builder.Eq{"col_name": "col1"}) - where = where.Or(builder.And(builder.In("col_name", "col1", "col2"), builder.Expr("col_name = ?", "col1"))) + where = where.And(builder.Eq{q("col_name"): "col1"}) + where = where.Or(builder.And(builder.In(q("col_name"), "col1", "col2"), builder.Expr(q("col_name")+" = ?", "col1"))) } conds = make([]Condition, 0) @@ -215,7 +216,7 @@ func TestFindAndCount(t *testing.T) { assert.NoError(t, err) var results []FindAndCount - sess := testEngine.Where("name = ?", "test1") + sess := testEngine.Where("`name` = ?", "test1") conds := sess.Conds() err = sess.Find(&results) assert.NoError(t, err) diff --git a/integrations/session_count_test.go b/integrations/session_count_test.go index 1517dede..e49b6045 100644 --- a/integrations/session_count_test.go +++ b/integrations/session_count_test.go @@ -63,7 +63,7 @@ func TestSQLCount(t *testing.T) { assertSync(t, new(UserinfoCount2), new(UserinfoBooks)) - total, err := testEngine.SQL("SELECT count(id) FROM " + testEngine.TableName("userinfo_count2", true)). + total, err := testEngine.SQL("SELECT count(`id`) FROM " + testEngine.Quote(testEngine.TableName("userinfo_count2", true))). Count() assert.NoError(t, err) assert.EqualValues(t, 0, total) @@ -89,7 +89,7 @@ func TestCountWithOthers(t *testing.T) { }) assert.NoError(t, err) - total, err := testEngine.OrderBy("id desc").Limit(1).Count(new(CountWithOthers)) + total, err := testEngine.OrderBy("`id` desc").Limit(1).Count(new(CountWithOthers)) assert.NoError(t, err) assert.EqualValues(t, 2, total) } @@ -118,11 +118,11 @@ func TestWithTableName(t *testing.T) { }) assert.NoError(t, err) - total, err := testEngine.OrderBy("id desc").Count(new(CountWithTableName)) + total, err := testEngine.OrderBy("`id` desc").Count(new(CountWithTableName)) assert.NoError(t, err) assert.EqualValues(t, 2, total) - total, err = testEngine.OrderBy("id desc").Count(CountWithTableName{}) + total, err = testEngine.OrderBy("`id` desc").Count(CountWithTableName{}) assert.NoError(t, err) assert.EqualValues(t, 2, total) } @@ -146,7 +146,7 @@ func TestCountWithSelectCols(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, total) - total, err = testEngine.Select("count(id)").Count(CountWithTableName{}) + total, err = testEngine.Select("count(`id`)").Count(CountWithTableName{}) assert.NoError(t, err) assert.EqualValues(t, 2, total) } @@ -166,7 +166,7 @@ func TestCountWithGroupBy(t *testing.T) { }) assert.NoError(t, err) - cnt, err := testEngine.GroupBy("name").Count(new(CountWithTableName)) + cnt, err := testEngine.GroupBy("`name`").Count(new(CountWithTableName)) assert.NoError(t, err) assert.EqualValues(t, 2, cnt) } diff --git a/integrations/session_exist_test.go b/integrations/session_exist_test.go index 29546376..a0f65211 100644 --- a/integrations/session_exist_test.go +++ b/integrations/session_exist_test.go @@ -48,19 +48,19 @@ func TestExistStruct(t *testing.T) { assert.NoError(t, err) assert.False(t, has) - has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{}) + has, err = testEngine.Where("`name` = ?", "test1").Exist(&RecordExist{}) assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.Where("name = ?", "test2").Exist(&RecordExist{}) + has, err = testEngine.Where("`name` = ?", "test2").Exist(&RecordExist{}) assert.NoError(t, err) assert.False(t, has) - has, err = testEngine.SQL("select * from "+testEngine.TableName("record_exist", true)+" where name = ?", "test1").Exist() + has, err = testEngine.SQL("select * from "+testEngine.Quote(testEngine.TableName("record_exist", true))+" where `name` = ?", "test1").Exist() assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.SQL("select * from "+testEngine.TableName("record_exist", true)+" where name = ?", "test2").Exist() + has, err = testEngine.SQL("select * from "+testEngine.Quote(testEngine.TableName("record_exist", true))+" where `name` = ?", "test2").Exist() assert.NoError(t, err) assert.False(t, has) @@ -68,11 +68,11 @@ func TestExistStruct(t *testing.T) { assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist() + has, err = testEngine.Table("record_exist").Where("`name` = ?", "test1").Exist() assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.Table("record_exist").Where("name = ?", "test2").Exist() + has, err = testEngine.Table("record_exist").Where("`name` = ?", "test2").Exist() assert.NoError(t, err) assert.False(t, has) @@ -124,43 +124,43 @@ func TestExistStructForJoin(t *testing.T) { defer session.Close() session.Table("number"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("number.lid = ?", 1) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`number`.`lid` = ?", 1) has, err := session.Exist() assert.NoError(t, err) assert.True(t, has) session.Table("number"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("number.lid = ?", 2) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`number`.`lid` = ?", 2) has, err = session.Exist() assert.NoError(t, err) assert.False(t, has) session.Table("number"). - Select("order_list.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("order_list.id = ?", 1) + Select("`order_list`.`id`"). + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`order_list`.`id` = ?", 1) has, err = session.Exist() assert.NoError(t, err) assert.True(t, has) session.Table("number"). Select("player.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("player.id = ?", 2) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`player`.`id` = ?", 2) has, err = session.Exist() assert.NoError(t, err) assert.False(t, has) session.Table("number"). Select("player.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid") + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`") has, err = session.Exist() assert.NoError(t, err) assert.True(t, has) @@ -174,15 +174,15 @@ func TestExistStructForJoin(t *testing.T) { session.Table("number"). Select("player.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid") + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`") has, err = session.Exist() assert.Error(t, err) assert.False(t, has) session.Table("number"). Select("player.id"). - Join("LEFT", "player", "player.id = number.lid") + Join("LEFT", "player", "`player`.`id` = `number`.`lid`") has, err = session.Exist() assert.NoError(t, err) assert.True(t, has) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 1cbf5e42..9b503f25 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -56,8 +56,8 @@ func TestJoinLimit(t *testing.T) { var salaries []Salary err = testEngine.Table("salary"). - Join("INNER", "check_list", "check_list.id = salary.lid"). - Join("LEFT", "empsetting", "empsetting.id = check_list.eid"). + Join("INNER", "check_list", "`check_list`.`id` = `salary`.`lid`"). + Join("LEFT", "empsetting", "`empsetting`.`id` = `check_list`.`eid`"). Limit(10, 0). Find(&salaries) assert.NoError(t, err) @@ -69,10 +69,10 @@ func TestWhere(t *testing.T) { assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) - err := testEngine.Where("id > ?", 2).Find(&users) + err := testEngine.Where("`id` > ?", 2).Find(&users) assert.NoError(t, err) - err = testEngine.Where("id > ?", 2).And("id < ?", 10).Find(&users) + err = testEngine.Where("`id` > ?", 2).And("`id` < ?", 10).Find(&users) assert.NoError(t, err) } @@ -125,50 +125,50 @@ func TestFind3(t *testing.T) { assert.NoError(t, err) var teams []Team - err = testEngine.Cols("`team`.id"). - Where("`team_user`.org_id=?", 1). - And("`team_user`.uid=?", 2). - Join("INNER", "`team_user`", "`team_user`.team_id=`team`.id"). + err = testEngine.Cols("`team`.`id`"). + Where("`team_user`.`org_id`=?", 1). + And("`team_user`.`uid`=?", 2). + Join("INNER", "`team_user`", "`team_user`.`team_id`=`team`.`id`"). Find(&teams) assert.NoError(t, err) teams = make([]Team, 0) err = testEngine.Cols("`team`.id"). - Where("`team_user`.org_id=?", 1). - And("`team_user`.uid=?", 2). - Join("INNER", teamUser, "`team_user`.team_id=`team`.id"). + Where("`team_user`.`org_id`=?", 1). + And("`team_user`.`uid`=?", 2). + Join("INNER", teamUser, "`team_user`.`team_id`=`team`.`id`"). Find(&teams) assert.NoError(t, err) teams = make([]Team, 0) - err = testEngine.Cols("`team`.id"). - Where("`team_user`.org_id=?", 1). - And("`team_user`.uid=?", 2). - Join("INNER", []interface{}{teamUser}, "`team_user`.team_id=`team`.id"). + err = testEngine.Cols("`team`.`id`"). + Where("`team_user`.`org_id`=?", 1). + And("`team_user`.`uid`=?", 2). + Join("INNER", []interface{}{teamUser}, "`team_user`.`team_id`=`team`.`id`"). Find(&teams) assert.NoError(t, err) teams = make([]Team, 0) - err = testEngine.Cols("`team`.id"). - Where("`tu`.org_id=?", 1). - And("`tu`.uid=?", 2). - Join("INNER", []string{"team_user", "tu"}, "`tu`.team_id=`team`.id"). + err = testEngine.Cols("`team`.`id`"). + Where("`tu`.`org_id`=?", 1). + And("`tu`.`uid`=?", 2). + Join("INNER", []string{"team_user", "tu"}, "`tu`.`team_id`=`team`.`id`"). Find(&teams) assert.NoError(t, err) teams = make([]Team, 0) - err = testEngine.Cols("`team`.id"). - Where("`tu`.org_id=?", 1). - And("`tu`.uid=?", 2). - Join("INNER", []interface{}{"team_user", "tu"}, "`tu`.team_id=`team`.id"). + err = testEngine.Cols("`team`.`id`"). + Where("`tu`.`org_id`=?", 1). + And("`tu`.`uid`=?", 2). + Join("INNER", []interface{}{"team_user", "tu"}, "`tu`.`team_id`=`team`.`id`"). Find(&teams) assert.NoError(t, err) teams = make([]Team, 0) - err = testEngine.Cols("`team`.id"). - Where("`tu`.org_id=?", 1). - And("`tu`.uid=?", 2). - Join("INNER", []interface{}{teamUser, "tu"}, "`tu`.team_id=`team`.id"). + err = testEngine.Cols("`team`.`id`"). + Where("`tu`.`org_id`=?", 1). + And("`tu`.`uid`=?", 2). + Join("INNER", []interface{}{teamUser, "tu"}, "`tu`.`team_id`=`team`.`id`"). Find(&teams) assert.NoError(t, err) } @@ -241,7 +241,7 @@ func TestOrder(t *testing.T) { assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) - err := testEngine.OrderBy("id desc").Find(&users) + err := testEngine.OrderBy("`id` desc").Find(&users) assert.NoError(t, err) users2 := make([]Userinfo, 0) @@ -254,7 +254,7 @@ func TestGroupBy(t *testing.T) { assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) - err := testEngine.GroupBy("id, username").Find(&users) + err := testEngine.GroupBy("`id`, `username`").Find(&users) assert.NoError(t, err) } @@ -263,7 +263,7 @@ func TestHaving(t *testing.T) { assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) - err := testEngine.GroupBy("username").Having("username='xlw'").Find(&users) + err := testEngine.GroupBy("`username`").Having("`username`='xlw'").Find(&users) assert.NoError(t, err) } @@ -499,7 +499,7 @@ func TestFindAndCountOneFunc(t *testing.T) { assert.EqualValues(t, 2, cnt) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).FindAndCount(&results) + cnt, err = testEngine.Where("`msg` = ?", true).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, cnt) @@ -549,21 +549,21 @@ func TestFindAndCountOneFunc(t *testing.T) { }, results[0]) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg"). + cnt, err = testEngine.Where("`msg` = ?", true).Select("`id`, `content`, `msg`"). Limit(1).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, cnt) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).Cols("id", "content", "msg"). + cnt, err = testEngine.Where("`msg` = ?", true).Cols("id", "content", "msg"). Limit(1).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, cnt) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).Desc("id"). + cnt, err = testEngine.Where("`msg` = ?", true).Desc("id"). Limit(1).Cols("content").FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) @@ -649,7 +649,7 @@ func TestFindAndCount2(t *testing.T) { cnt, err = testEngine. Table(new(TestFindAndCountHotel)). Alias("t"). - Where("t.region like '6501%'"). + Where("`t`.`region` like '6501%'"). Limit(10, 0). FindAndCount(&hotels) assert.NoError(t, err) @@ -705,7 +705,7 @@ func TestFindAndCountWithGroupBy(t *testing.T) { assert.NoError(t, err) var results []FindAndCountWithGroupBy - cnt, err := testEngine.GroupBy("age").FindAndCount(&results) + cnt, err := testEngine.GroupBy("`age`").FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 2, cnt) assert.EqualValues(t, 2, len(results)) @@ -735,14 +735,14 @@ func TestFindMapStringId(t *testing.T) { deviceMaps := make(map[string]*FindMapDevice, len(deviceIDs)) err = testEngine. - Where("status = ?", 1). + Where("`status` = ?", 1). In("deviceid", deviceIDs). Find(&deviceMaps) assert.NoError(t, err) deviceMaps2 := make(map[string]FindMapDevice, len(deviceIDs)) err = testEngine. - Where("status = ?", 1). + Where("`status` = ?", 1). In("deviceid", deviceIDs). Find(&deviceMaps2) assert.NoError(t, err) @@ -919,17 +919,17 @@ func TestFindJoin(t *testing.T) { assertSync(t, new(SceneItem), new(DeviceUserPrivrels), new(Order)) var scenes []SceneItem - err := testEngine.Join("LEFT OUTER", "device_user_privrels", "device_user_privrels.device_id=scene_item.device_id"). - Where("scene_item.type=?", 3).Or("device_user_privrels.user_id=?", 339).Find(&scenes) + err := testEngine.Join("LEFT OUTER", "device_user_privrels", "`device_user_privrels`.`device_id`=`scene_item`.`device_id`"). + Where("`scene_item`.`type`=?", 3).Or("`device_user_privrels`.`user_id`=?", 339).Find(&scenes) assert.NoError(t, err) scenes = make([]SceneItem, 0) - err = testEngine.Join("LEFT OUTER", new(DeviceUserPrivrels), "device_user_privrels.device_id=scene_item.device_id"). - Where("scene_item.type=?", 3).Or("device_user_privrels.user_id=?", 339).Find(&scenes) + err = testEngine.Join("LEFT OUTER", new(DeviceUserPrivrels), "`device_user_privrels`.`device_id`=`scene_item`.`device_id`"). + Where("`scene_item`.`type`=?", 3).Or("`device_user_privrels`.`user_id`=?", 339).Find(&scenes) assert.NoError(t, err) scenes = make([]SceneItem, 0) - err = testEngine.Join("INNER", "order", "`scene_item`.device_id=`order`.id").Find(&scenes) + err = testEngine.Join("INNER", "order", "`scene_item`.`device_id`=`order`.`id`").Find(&scenes) assert.NoError(t, err) } @@ -949,7 +949,7 @@ func TestJoinFindLimit(t *testing.T) { assertSync(t, new(JoinFindLimit1), new(JoinFindLimit2)) var finds []JoinFindLimit1 - err := testEngine.Join("INNER", new(JoinFindLimit2), "join_find_limit2.eid=join_find_limit1.id"). + err := testEngine.Join("INNER", new(JoinFindLimit2), "`join_find_limit2`.`eid`=`join_find_limit1`.`id`"). Limit(10, 10).Find(&finds) assert.NoError(t, err) } @@ -981,9 +981,9 @@ func TestMoreExtends(t *testing.T) { assertSync(t, new(MoreExtendsUsers), new(MoreExtendsBooks)) var books []MoreExtendsBooksExtend - err := testEngine.Table("more_extends_books").Select("more_extends_books.*, more_extends_users.*"). - Join("INNER", "more_extends_users", "more_extends_books.user_id = more_extends_users.id"). - Where("more_extends_books.name LIKE ?", "abc"). + err := testEngine.Table("more_extends_books").Select("`more_extends_books`.*, `more_extends_users`.*"). + Join("INNER", "more_extends_users", "`more_extends_books`.`user_id` = `more_extends_users`.`id`"). + Where("`more_extends_books`.`name` LIKE ?", "abc"). Limit(10, 10). Find(&books) assert.NoError(t, err) @@ -991,9 +991,9 @@ func TestMoreExtends(t *testing.T) { books = make([]MoreExtendsBooksExtend, 0, len(books)) err = testEngine.Table("more_extends_books"). Alias("m"). - Select("m.*, more_extends_users.*"). - Join("INNER", "more_extends_users", "m.user_id = more_extends_users.id"). - Where("m.name LIKE ?", "abc"). + Select("`m`.*, `more_extends_users`.*"). + Join("INNER", "more_extends_users", "`m`.`user_id` = `more_extends_users`.`id`"). + Where("`m`.`name` LIKE ?", "abc"). Limit(10, 10). Find(&books) assert.NoError(t, err) @@ -1038,11 +1038,11 @@ func TestUpdateFind(t *testing.T) { } _, err := session.Insert(&tuf) assert.NoError(t, err) - _, err = session.Where("id = ?", tuf.Id).Update(&TestUpdateFind{}) + _, err = session.Where("`id` = ?", tuf.Id).Update(&TestUpdateFind{}) assert.EqualError(t, xorm.ErrNoColumnsTobeUpdated, err.Error()) var tufs []TestUpdateFind - err = session.Where("id = ?", tuf.Id).Find(&tufs) + err = session.Where("`id` = ?", tuf.Id).Find(&tufs) assert.NoError(t, err) } diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 99ecd462..4fc30adb 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -15,6 +15,7 @@ import ( "xorm.io/xorm" "xorm.io/xorm/contexts" "xorm.io/xorm/convert" + "xorm.io/xorm/dialects" "xorm.io/xorm/schemas" "github.com/shopspring/decimal" @@ -55,15 +56,15 @@ func TestGetVar(t *testing.T) { assert.Equal(t, 28, age) var ageMax int - has, err = testEngine.SQL("SELECT max(age) FROM "+testEngine.TableName("get_var", true)+" WHERE `id` = ?", data.Id).Get(&ageMax) + has, err = testEngine.SQL("SELECT max(`age`) FROM "+testEngine.Quote(testEngine.TableName("get_var", true))+" WHERE `id` = ?", data.Id).Get(&ageMax) assert.NoError(t, err) assert.Equal(t, true, has) assert.Equal(t, 28, ageMax) var age2 int64 has, err = testEngine.Table("get_var").Cols("age"). - Where("age > ?", 20). - And("age < ?", 30). + Where("`age` > ?", 20). + And("`age` < ?", 30). Get(&age2) assert.NoError(t, err) assert.Equal(t, true, has) @@ -77,8 +78,8 @@ func TestGetVar(t *testing.T) { var age4 int16 has, err = testEngine.Table("get_var").Cols("age"). - Where("age > ?", 20). - And("age < ?", 30). + Where("`age` > ?", 20). + And("`age` < ?", 30). Get(&age4) assert.NoError(t, err) assert.Equal(t, true, has) @@ -86,8 +87,8 @@ func TestGetVar(t *testing.T) { var age5 int32 has, err = testEngine.Table("get_var").Cols("age"). - Where("age > ?", 20). - And("age < ?", 30). + Where("`age` > ?", 20). + And("`age` < ?", 30). Get(&age5) assert.NoError(t, err) assert.Equal(t, true, has) @@ -101,8 +102,8 @@ func TestGetVar(t *testing.T) { var age7 int64 has, err = testEngine.Table("get_var").Cols("age"). - Where("age > ?", 20). - And("age < ?", 30). + Where("`age` > ?", 20). + And("`age` < ?", 30). Get(&age7) assert.NoError(t, err) assert.Equal(t, true, has) @@ -116,8 +117,8 @@ func TestGetVar(t *testing.T) { var age9 int16 has, err = testEngine.Table("get_var").Cols("age"). - Where("age > ?", 20). - And("age < ?", 30). + Where("`age` > ?", 20). + And("`age` < ?", 30). Get(&age9) assert.NoError(t, err) assert.Equal(t, true, has) @@ -125,8 +126,8 @@ func TestGetVar(t *testing.T) { var age10 int32 has, err = testEngine.Table("get_var").Cols("age"). - Where("age > ?", 20). - And("age < ?", 30). + Where("`age` > ?", 20). + And("`age` < ?", 30). Get(&age10) assert.NoError(t, err) assert.Equal(t, true, has) @@ -161,16 +162,16 @@ func TestGetVar(t *testing.T) { var money2 float64 if testEngine.Dialect().URI().DBType == schemas.MSSQL { - has, err = testEngine.SQL("SELECT TOP 1 money FROM " + testEngine.TableName("get_var", true)).Get(&money2) + has, err = testEngine.SQL("SELECT TOP 1 `money` FROM " + testEngine.Quote(testEngine.TableName("get_var", true))).Get(&money2) } else { - has, err = testEngine.SQL("SELECT money FROM " + testEngine.TableName("get_var", true) + " LIMIT 1").Get(&money2) + has, err = testEngine.SQL("SELECT `money` FROM " + testEngine.Quote(testEngine.TableName("get_var", true)) + " LIMIT 1").Get(&money2) } assert.NoError(t, err) assert.Equal(t, true, has) assert.Equal(t, "1.5", fmt.Sprintf("%.1f", money2)) var money3 float64 - has, err = testEngine.SQL("SELECT money FROM " + testEngine.TableName("get_var", true) + " WHERE money > 20").Get(&money3) + has, err = testEngine.SQL("SELECT `money` FROM " + testEngine.Quote(testEngine.TableName("get_var", true)) + " WHERE `money` > 20").Get(&money3) assert.NoError(t, err) assert.Equal(t, false, has) @@ -187,7 +188,7 @@ func TestGetVar(t *testing.T) { // for mymysql driver, interface{} will be []byte, so ignore it currently if testEngine.DriverName() != "mymysql" { var valuesInter = make(map[string]interface{}) - has, err = testEngine.Table("get_var").Where("id = ?", 1).Select("*").Get(&valuesInter) + has, err = testEngine.Table("get_var").Where("`id` = ?", 1).Select("*").Get(&valuesInter) assert.NoError(t, err) assert.Equal(t, true, has) assert.Equal(t, 5, len(valuesInter)) @@ -243,7 +244,7 @@ func TestGetStruct(t *testing.T) { if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) - _, err = session.Exec("SET IDENTITY_INSERT userinfo_get ON") + _, err = session.Exec("SET IDENTITY_INSERT `userinfo_get` ON") assert.NoError(t, err) } cnt, err := session.Insert(&UserinfoGet{Uid: 2}) @@ -300,6 +301,11 @@ func TestGetSlice(t *testing.T) { func TestGetMap(t *testing.T) { assert.NoError(t, PrepareEngine()) + if testEngine.Dialect().Features().AutoincrMode == dialects.SequenceAutoincrMode { + t.SkipNow() + return + } + type UserinfoMap struct { Uid int `xorm:"pk autoincr"` IsMan bool @@ -308,7 +314,7 @@ func TestGetMap(t *testing.T) { assertSync(t, new(UserinfoMap)) tableName := testEngine.Quote(testEngine.TableName("userinfo_map", true)) - _, err := testEngine.Exec(fmt.Sprintf("INSERT INTO %s (is_man) VALUES (NULL)", tableName)) + _, err := testEngine.Exec(fmt.Sprintf("INSERT INTO %s (`is_man`) VALUES (NULL)", tableName)) assert.NoError(t, err) var valuesString = make(map[string]string) @@ -479,7 +485,7 @@ func TestGetStructId(t *testing.T) { //var id int64 var maxid maxidst - sql := "select max(id) as id from " + testEngine.TableName(&TestGetStruct{}, true) + sql := "select max(`id`) as id from " + testEngine.Quote(testEngine.TableName(&TestGetStruct{}, true)) has, err := testEngine.SQL(sql).Get(&maxid) assert.NoError(t, err) assert.True(t, has) @@ -597,73 +603,78 @@ func TestGetNullVar(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(TestGetNullVarStruct)) - affected, err := testEngine.Exec("insert into " + testEngine.TableName(new(TestGetNullVarStruct), true) + " (name,age) values (null,null)") + if testEngine.Dialect().Features().AutoincrMode == dialects.SequenceAutoincrMode { + t.SkipNow() + return + } + + affected, err := testEngine.Exec("insert into " + testEngine.Quote(testEngine.TableName(new(TestGetNullVarStruct), true)) + " (`name`,`age`) values (null,null)") assert.NoError(t, err) a, _ := affected.RowsAffected() assert.EqualValues(t, 1, a) var name string - has, err := testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("name").Get(&name) + has, err := testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("name").Get(&name) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, "", name) var age int - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age) var age2 int8 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age2) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age2) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age2) var age3 int16 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age3) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age3) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age3) var age4 int32 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age4) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age4) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age4) var age5 int64 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age5) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age5) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age5) var age6 uint - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age6) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age6) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age6) var age7 uint8 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age7) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age7) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age7) var age8 int16 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age8) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age8) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age8) var age9 int32 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age9) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age9) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age9) var age10 int64 - has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("id = ?", 1).Cols("age").Get(&age10) + has, err = testEngine.Table(new(TestGetNullVarStruct)).Where("`id` = ?", 1).Cols("age").Get(&age10) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 0, age10) @@ -697,7 +708,7 @@ func TestCustomTypes(t *testing.T) { assert.EqualValues(t, "test", name) var age MyInt - has, err = testEngine.Table(new(TestCustomizeStruct)).ID(s.Id).Select("age").Get(&age) + has, err = testEngine.Table(new(TestCustomizeStruct)).ID(s.Id).Select("`age`").Get(&age) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 32, age) @@ -759,7 +770,7 @@ func TestGetBigFloat(t *testing.T) { assert.NoError(t, err) var m big.Float - has, err := testEngine.Table("get_big_float").Cols("money").Where("id=?", gf.Id).Get(&m) + has, err := testEngine.Table("get_big_float").Cols("money").Where("`id`=?", gf.Id).Get(&m) assert.NoError(t, err) assert.True(t, has) assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) @@ -785,7 +796,7 @@ func TestGetBigFloat(t *testing.T) { assert.NoError(t, err) var m2 big.Float - has, err := testEngine.Table("get_big_float2").Cols("money").Where("id=?", gf2.Id).Get(&m2) + has, err := testEngine.Table("get_big_float2").Cols("money").Where("`id`=?", gf2.Id).Get(&m2) assert.NoError(t, err) assert.True(t, has) assert.True(t, m2.String() == gf2.Money.String(), "%v != %v", m2.String(), gf2.Money.String()) @@ -825,7 +836,7 @@ func TestGetDecimal(t *testing.T) { assert.NoError(t, err) var m decimal.Decimal - has, err := testEngine.Table("get_decimal").Cols("money").Where("id=?", gf.Id).Get(&m) + has, err := testEngine.Table("get_decimal").Cols("money").Where("`id`=?", gf.Id).Get(&m) assert.NoError(t, err) assert.True(t, has) assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) @@ -850,7 +861,7 @@ func TestGetDecimal(t *testing.T) { assert.NoError(t, err) var m decimal.Decimal - has, err := testEngine.Table("get_decimal2").Cols("money").Where("id=?", gf.Id).Get(&m) + has, err := testEngine.Table("get_decimal2").Cols("money").Where("`id`=?", gf.Id).Get(&m) assert.NoError(t, err) assert.True(t, has) assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index cd56a958..70ec13f3 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -11,6 +11,7 @@ import ( "time" "xorm.io/xorm" + "xorm.io/xorm/schemas" "github.com/stretchr/testify/assert" ) @@ -191,8 +192,8 @@ func TestInsertDefault(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, -1, di.Status) - assert.EqualValues(t, di2.Updated.Unix(), di.Updated.Unix()) - assert.EqualValues(t, di2.Created.Unix(), di.Created.Unix()) + assert.EqualValues(t, di2.Updated.Unix(), di.Updated.Unix(), di.Updated) + assert.EqualValues(t, di2.Created.Unix(), di.Created.Unix(), di.Created) } func TestInsertDefault2(t *testing.T) { @@ -624,6 +625,11 @@ func TestAnonymousStruct(t *testing.T) { } func TestInsertMap(t *testing.T) { + if testEngine.Dialect().URI().DBType == schemas.DAMENG { + t.SkipNow() + return + } + type InsertMap struct { Id int64 Width uint32 @@ -727,7 +733,7 @@ func TestInsertWhere(t *testing.T) { } inserted, err := testEngine.SetExpr("`index`", "coalesce(MAX(`index`),0)+1"). - Where("repo_id=?", 1). + Where("`repo_id`=?", 1). Insert(&i) assert.NoError(t, err) assert.EqualValues(t, 1, inserted) @@ -740,7 +746,12 @@ func TestInsertWhere(t *testing.T) { i.Index = 1 assert.EqualValues(t, i, j) - inserted, err = testEngine.Table(new(InsertWhere)).Where("repo_id=?", 1). + if testEngine.Dialect().URI().DBType == schemas.DAMENG { + t.SkipNow() + return + } + + inserted, err = testEngine.Table(new(InsertWhere)).Where("`repo_id`=?", 1). SetExpr("`index`", "coalesce(MAX(`index`),0)+1"). Insert(map[string]interface{}{ "repo_id": 1, @@ -761,7 +772,7 @@ func TestInsertWhere(t *testing.T) { assert.EqualValues(t, "trest2", j2.Name) assert.EqualValues(t, 2, j2.Index) - inserted, err = testEngine.Table(new(InsertWhere)).Where("repo_id=?", 1). + inserted, err = testEngine.Table(new(InsertWhere)).Where("`repo_id`=?", 1). SetExpr("`index`", "coalesce(MAX(`index`),0)+1"). SetExpr("repo_id", "1"). Insert(map[string]string{ @@ -777,7 +788,7 @@ func TestInsertWhere(t *testing.T) { assert.EqualValues(t, "trest3", j3.Name) assert.EqualValues(t, 3, j3.Index) - inserted, err = testEngine.Table(new(InsertWhere)).Where("repo_id=?", 1). + inserted, err = testEngine.Table(new(InsertWhere)).Where("`repo_id`=?", 1). SetExpr("`index`", "coalesce(MAX(`index`),0)+1"). Insert(map[string]interface{}{ "repo_id": 1, @@ -793,7 +804,7 @@ func TestInsertWhere(t *testing.T) { assert.EqualValues(t, "10';delete * from insert_where; --", j4.Name) assert.EqualValues(t, 4, j4.Index) - inserted, err = testEngine.Table(new(InsertWhere)).Where("repo_id=?", 1). + inserted, err = testEngine.Table(new(InsertWhere)).Where("`repo_id`=?", 1). SetExpr("`index`", "coalesce(MAX(`index`),0)+1"). Insert(map[string]interface{}{ "repo_id": 1, @@ -846,6 +857,11 @@ func TestInsertExpr2(t *testing.T) { assert.EqualValues(t, 1, ie2.RepoId) assert.EqualValues(t, true, ie2.IsTag) + if testEngine.Dialect().URI().DBType == schemas.DAMENG { + t.SkipNow() + return + } + inserted, err = testEngine.Table(new(InsertExprsRelease)). SetExpr("is_draft", true). SetExpr("num_commits", 0). @@ -1067,6 +1083,11 @@ func TestInsertDeleted(t *testing.T) { } func TestInsertMultipleMap(t *testing.T) { + if testEngine.Dialect().URI().DBType == schemas.DAMENG { + t.SkipNow() + return + } + type InsertMultipleMap struct { Id int64 Width uint32 diff --git a/integrations/session_iterate_test.go b/integrations/session_iterate_test.go index 564f457b..95f5a282 100644 --- a/integrations/session_iterate_test.go +++ b/integrations/session_iterate_test.go @@ -91,7 +91,7 @@ func TestBufferIterate(t *testing.T) { assert.EqualValues(t, 7, cnt) cnt = 0 - err = testEngine.Where("id <= 10").BufferSize(2).Iterate(new(UserBufferIterate), func(i int, bean interface{}) error { + err = testEngine.Where("`id` <= 10").BufferSize(2).Iterate(new(UserBufferIterate), func(i int, bean interface{}) error { user := bean.(*UserBufferIterate) assert.EqualValues(t, cnt+1, user.Id) assert.EqualValues(t, true, user.IsMan) diff --git a/integrations/session_query_test.go b/integrations/session_query_test.go index ef2ccdd6..edc77aec 100644 --- a/integrations/session_query_test.go +++ b/integrations/session_query_test.go @@ -5,7 +5,6 @@ package integrations import ( - "fmt" "strconv" "testing" "time" @@ -37,7 +36,7 @@ func TestQueryString(t *testing.T) { _, err := testEngine.InsertOne(data) assert.NoError(t, err) - records, err := testEngine.QueryString("select * from " + testEngine.TableName("get_var2", true)) + records, err := testEngine.QueryString("select * from " + testEngine.Quote(testEngine.TableName("get_var2", true))) assert.NoError(t, err) assert.Equal(t, 1, len(records)) assert.Equal(t, 5, len(records[0])) @@ -63,7 +62,7 @@ func TestQueryString2(t *testing.T) { _, err := testEngine.Insert(data) assert.NoError(t, err) - records, err := testEngine.QueryString("select * from " + testEngine.TableName("get_var3", true)) + records, err := testEngine.QueryString("select * from " + testEngine.Quote(testEngine.TableName("get_var3", true))) assert.NoError(t, err) assert.Equal(t, 1, len(records)) assert.Equal(t, 2, len(records[0])) @@ -71,42 +70,6 @@ func TestQueryString2(t *testing.T) { assert.True(t, "0" == records[0]["msg"] || "false" == records[0]["msg"]) } -func toString(i interface{}) string { - switch i.(type) { - case []byte: - return string(i.([]byte)) - case string: - return i.(string) - } - return fmt.Sprintf("%v", i) -} - -func toInt64(i interface{}) int64 { - switch i.(type) { - case []byte: - n, _ := strconv.ParseInt(string(i.([]byte)), 10, 64) - return n - case int: - return int64(i.(int)) - case int64: - return i.(int64) - } - return 0 -} - -func toFloat64(i interface{}) float64 { - switch i.(type) { - case []byte: - n, _ := strconv.ParseFloat(string(i.([]byte)), 64) - return n - case float64: - return i.(float64) - case float32: - return float64(i.(float32)) - } - return 0 -} - func toBool(i interface{}) bool { switch t := i.(type) { case int32: @@ -138,7 +101,7 @@ func TestQueryInterface(t *testing.T) { _, err := testEngine.InsertOne(data) assert.NoError(t, err) - records, err := testEngine.QueryInterface("select * from " + testEngine.TableName("get_var_interface", true)) + records, err := testEngine.QueryInterface("select * from " + testEngine.Quote(testEngine.TableName("get_var_interface", true))) assert.NoError(t, err) assert.Equal(t, 1, len(records)) assert.Equal(t, 5, len(records[0])) @@ -192,7 +155,7 @@ func TestQueryNoParams(t *testing.T) { assert.NoError(t, err) assertResult(t, results) - results, err = testEngine.SQL("select * from " + testEngine.TableName("query_no_params", true)).Query() + results, err = testEngine.SQL("select * from " + testEngine.Quote(testEngine.TableName("query_no_params", true))).Query() assert.NoError(t, err) assertResult(t, results) } @@ -223,7 +186,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.EqualValues(t, "0", records[0]["msg"]) } - records, err = testEngine.Table("get_var4").Where(builder.Eq{"id": 1}).QueryString() + records, err = testEngine.Table("get_var4").Where(builder.Eq{"`id`": 1}).QueryString() assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) @@ -260,7 +223,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.EqualValues(t, "0", records[0][1]) } - records, err = testEngine.Table("get_var6").Where(builder.Eq{"id": 1}).QuerySliceString() + records, err = testEngine.Table("get_var6").Where(builder.Eq{"`id`": 1}).QuerySliceString() assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) @@ -293,7 +256,7 @@ func TestQueryInterfaceNoParam(t *testing.T) { assert.EqualValues(t, 1, records[0]["id"]) assert.False(t, toBool(records[0]["msg"])) - records, err = testEngine.Table("get_var5").Where(builder.Eq{"id": 1}).QueryInterface() + records, err = testEngine.Table("get_var5").Where(builder.Eq{"`id`": 1}).QueryInterface() assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, 1, records[0]["id"]) @@ -340,7 +303,7 @@ func TestQueryWithBuilder(t *testing.T) { assert.EqualValues(t, 3000, money) } - results, err := testEngine.Query(builder.Select("*").From(testEngine.TableName("query_with_builder", true))) + results, err := testEngine.Query(builder.Select("*").From(testEngine.Quote(testEngine.TableName("query_with_builder", true)))) assert.NoError(t, err) assertResult(t, results) } @@ -383,14 +346,14 @@ func TestJoinWithSubQuery(t *testing.T) { tbName := testEngine.Quote(testEngine.TableName("join_with_sub_query_depart", true)) var querys []JoinWithSubQuery1 - err = testEngine.Join("INNER", builder.Select("id").From(tbName), - "join_with_sub_query_depart.id = join_with_sub_query1.depart_id").Find(&querys) + err = testEngine.Join("INNER", builder.Select("`id`").From(tbName), + "`join_with_sub_query_depart`.`id` = `join_with_sub_query1`.`depart_id`").Find(&querys) assert.NoError(t, err) assert.EqualValues(t, 1, len(querys)) assert.EqualValues(t, q, querys[0]) querys = make([]JoinWithSubQuery1, 0, 1) - err = testEngine.Join("INNER", "(SELECT id FROM "+tbName+") join_with_sub_query_depart", "join_with_sub_query_depart.id = join_with_sub_query1.depart_id"). + err = testEngine.Join("INNER", "(SELECT `id` FROM "+tbName+") `a`", "`a`.`id` = `join_with_sub_query1`.`depart_id`"). Find(&querys) assert.NoError(t, err) assert.EqualValues(t, 1, len(querys)) diff --git a/integrations/session_raw_test.go b/integrations/session_raw_test.go index 36677683..44718f46 100644 --- a/integrations/session_raw_test.go +++ b/integrations/session_raw_test.go @@ -22,13 +22,13 @@ func TestExecAndQuery(t *testing.T) { assert.NoError(t, testEngine.Sync2(new(UserinfoQuery))) - res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_query`", true)+" (uid, name) VALUES (?, ?)", 1, "user") + res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_query`", true)+" (`uid`, `name`) VALUES (?, ?)", 1, "user") assert.NoError(t, err) cnt, err := res.RowsAffected() assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - results, err := testEngine.Query("select * from " + testEngine.TableName("userinfo_query", true)) + results, err := testEngine.Query("select * from " + testEngine.Quote(testEngine.TableName("userinfo_query", true))) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) id, err := strconv.Atoi(string(results[0]["uid"])) @@ -48,19 +48,19 @@ func TestExecTime(t *testing.T) { assert.NoError(t, testEngine.Sync2(new(UserinfoExecTime))) now := time.Now() - res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_exec_time`", true)+" (uid, name, created) VALUES (?, ?, ?)", 1, "user", now) + res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_exec_time`", true)+" (`uid`, `name`, `created`) VALUES (?, ?, ?)", 1, "user", now) assert.NoError(t, err) cnt, err := res.RowsAffected() assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - results, err := testEngine.QueryString("SELECT * FROM " + testEngine.TableName("`userinfo_exec_time`", true)) + results, err := testEngine.QueryString("SELECT * FROM " + testEngine.Quote(testEngine.TableName("userinfo_exec_time", true))) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, now.In(testEngine.GetTZLocation()).Format("2006-01-02 15:04:05"), results[0]["created"]) var uet UserinfoExecTime - has, err := testEngine.Where("uid=?", 1).Get(&uet) + has, err := testEngine.Where("`uid`=?", 1).Get(&uet) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, now.In(testEngine.GetTZLocation()).Format("2006-01-02 15:04:05"), uet.Created.Format("2006-01-02 15:04:05")) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 9cbebcbf..98ad9657 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -526,8 +526,9 @@ func TestModifyColum(t *testing.T) { SQLType: schemas.SQLType{ Name: "VARCHAR", }, - Length: 16, - Nullable: false, + Length: 16, + Nullable: false, + DefaultIsEmpty: true, }) _, err := testEngine.Exec(alterSQL) assert.NoError(t, err) diff --git a/integrations/session_test.go b/integrations/session_test.go index c3ef0513..eacf2ff5 100644 --- a/integrations/session_test.go +++ b/integrations/session_test.go @@ -18,7 +18,7 @@ func TestClose(t *testing.T) { sess1.Close() assert.True(t, sess1.IsClosed()) - sess2 := testEngine.Where("a = ?", 1) + sess2 := testEngine.Where("`a` = ?", 1) sess2.Close() assert.True(t, sess2.IsClosed()) } diff --git a/integrations/session_tx_test.go b/integrations/session_tx_test.go index 4cff5610..8d6519d0 100644 --- a/integrations/session_tx_test.go +++ b/integrations/session_tx_test.go @@ -37,7 +37,7 @@ func TestTransaction(t *testing.T) { assert.NoError(t, err) user2 := Userinfo{Username: "yyy"} - _, err = session.Where("id = ?", 0).Update(&user2) + _, err = session.Where("`id` = ?", 0).Update(&user2) assert.NoError(t, err) _, err = session.Delete(&user2) @@ -70,10 +70,10 @@ func TestCombineTransaction(t *testing.T) { assert.NoError(t, err) user2 := Userinfo{Username: "zzz"} - _, err = session.Where("id = ?", 0).Update(&user2) + _, err = session.Where("`id` = ?", 0).Update(&user2) assert.NoError(t, err) - _, err = session.Exec("delete from "+testEngine.TableName("userinfo", true)+" where username = ?", user2.Username) + _, err = session.Exec("delete from "+testEngine.Quote(testEngine.TableName("userinfo", true))+" where `username` = ?", user2.Username) assert.NoError(t, err) err = session.Commit() @@ -113,10 +113,10 @@ func TestCombineTransactionSameMapper(t *testing.T) { assert.NoError(t, err) user2 := Userinfo{Username: "zzz"} - _, err = session.Where("id = ?", 0).Update(&user2) + _, err = session.Where("`id` = ?", 0).Update(&user2) assert.NoError(t, err) - _, err = session.Exec("delete from "+testEngine.TableName("`Userinfo`", true)+" where `Username` = ?", user2.Username) + _, err = session.Exec("delete from "+testEngine.Quote(testEngine.TableName("Userinfo", true))+" where `Username` = ?", user2.Username) assert.NoError(t, err) err = session.Commit() @@ -144,7 +144,7 @@ func TestMultipleTransaction(t *testing.T) { assert.NoError(t, err) user2 := MultipleTransaction{Name: "zzz"} - _, err = session.Where("id = ?", 0).Update(&user2) + _, err = session.Where("`id` = ?", 0).Update(&user2) assert.NoError(t, err) err = session.Commit() @@ -158,7 +158,7 @@ func TestMultipleTransaction(t *testing.T) { err = session.Begin() assert.NoError(t, err) - _, err = session.Where("id=?", m1.Id).Delete(new(MultipleTransaction)) + _, err = session.Where("`id`=?", m1.Id).Delete(new(MultipleTransaction)) assert.NoError(t, err) err = session.Commit() diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index bbcc7600..4312d0e0 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -35,7 +35,7 @@ func TestUpdateMap(t *testing.T) { _, err := testEngine.Insert(&tb) assert.NoError(t, err) - cnt, err := testEngine.Table("update_table").Where("id = ?", tb.Id).Update(map[string]interface{}{ + cnt, err := testEngine.Table("update_table").Where("`id` = ?", tb.Id).Update(map[string]interface{}{ "name": "test2", "age": 36, }) @@ -93,7 +93,12 @@ func TestUpdateLimit(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - cnt, err = testEngine.OrderBy("name desc").Limit(1).Update(&UpdateTable2{ + if testEngine.Dialect().URI().DBType == schemas.DAMENG { + t.SkipNow() + return + } + + cnt, err = testEngine.OrderBy("`name` desc").Limit(1).Update(&UpdateTable2{ Age: 30, }) assert.NoError(t, err) @@ -166,7 +171,7 @@ func TestForUpdate(t *testing.T) { // use lock fList := make([]ForUpdate, 0) session1.ForUpdate() - session1.Where("id = ?", 1) + session1.Where("`id` = ?", 1) err = session1.Find(&fList) switch { case err != nil: @@ -187,7 +192,7 @@ func TestForUpdate(t *testing.T) { wg.Add(1) go func() { f2 := new(ForUpdate) - session2.Where("id = ?", 1).ForUpdate() + session2.Where("`id` = ?", 1).ForUpdate() has, err := session2.Get(f2) // wait release lock switch { case err != nil: @@ -207,7 +212,7 @@ func TestForUpdate(t *testing.T) { wg2.Add(1) go func() { f3 := new(ForUpdate) - session3.Where("id = ?", 1) + session3.Where("`id` = ?", 1) has, err := session3.Get(f3) // wait release lock switch { case err != nil: @@ -225,7 +230,7 @@ func TestForUpdate(t *testing.T) { f := new(ForUpdate) f.Name = "updated by session1" - session1.Where("id = ?", 1) + session1.Where("`id` = ?", 1) session1.Update(f) // release lock @@ -300,7 +305,7 @@ func TestUpdateMap2(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(UpdateMustCols)) - _, err := testEngine.Table("update_must_cols").Where("id =?", 1).Update(map[string]interface{}{ + _, err := testEngine.Table("update_must_cols").Where("`id` =?", 1).Update(map[string]interface{}{ "bool": true, }) assert.NoError(t, err) @@ -345,11 +350,11 @@ func TestUpdate1(t *testing.T) { userID := user.Uid has, err := testEngine.ID(userID). - And("username = ?", user.Username). - And("height = ?", user.Height). - And("departname = ?", ""). - And("detail_id = ?", 0). - And("is_man = ?", false). + And("`username` = ?", user.Username). + And("`height` = ?", user.Height). + And("`departname` = ?", ""). + And("`detail_id` = ?", 0). + And("`is_man` = ?", false). Get(&Userinfo{}) assert.NoError(t, err) assert.True(t, has, "cannot insert properly") @@ -362,12 +367,12 @@ func TestUpdate1(t *testing.T) { assert.EqualValues(t, 1, cnt, "update not returned 1") has, err = testEngine.ID(userID). - And("username = ?", updatedUser.Username). - And("height IS NULL"). - And("departname IS NULL"). - And("is_man IS NULL"). - And("created IS NULL"). - And("detail_id = ?", 0). + And("`username` = ?", updatedUser.Username). + And("`height` IS NULL"). + And("`departname` IS NULL"). + And("`is_man` IS NULL"). + And("`created` IS NULL"). + And("`detail_id` = ?", 0). Get(&Userinfo{}) assert.NoError(t, err) assert.True(t, has, "cannot update with null properly") @@ -825,7 +830,7 @@ func TestNewUpdate(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 0, af) - af, err = testEngine.Table(new(TbUserInfo)).Where("phone=?", "13126564922").Update(&changeUsr) + af, err = testEngine.Table(new(TbUserInfo)).Where("`phone`=?", "13126564922").Update(&changeUsr) assert.NoError(t, err) assert.EqualValues(t, 0, af) } @@ -1166,7 +1171,7 @@ func TestUpdateExprs(t *testing.T) { }) assert.NoError(t, err) - _, err = testEngine.SetExpr("num_issues", "num_issues+1").AllCols().Update(&UpdateExprs{ + _, err = testEngine.SetExpr("num_issues", "`num_issues`+1").AllCols().Update(&UpdateExprs{ NumIssues: 3, Name: "lunny xiao", }) @@ -1197,7 +1202,7 @@ func TestUpdateAlias(t *testing.T) { }) assert.NoError(t, err) - _, err = testEngine.Alias("ua").Where("ua.id = ?", 1).Update(&UpdateAlias{ + _, err = testEngine.Alias("ua").Where("ua.`id` = ?", 1).Update(&UpdateAlias{ NumIssues: 2, Name: "lunny xiao", }) @@ -1237,7 +1242,7 @@ func TestUpdateExprs2(t *testing.T) { assert.EqualValues(t, 1, inserted) updated, err := testEngine. - Where("repo_id = ? AND is_tag = ?", 1, false). + Where("`repo_id` = ? AND `is_tag` = ?", 1, false). SetExpr("is_draft", true). SetExpr("num_commits", 0). SetExpr("sha1", ""). @@ -1257,6 +1262,11 @@ func TestUpdateExprs2(t *testing.T) { } func TestUpdateMap3(t *testing.T) { + if testEngine.Dialect().URI().DBType == schemas.DAMENG { + t.SkipNow() + return + } + assert.NoError(t, PrepareEngine()) type UpdateMapUser struct { @@ -1308,7 +1318,7 @@ func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { assertGetRecord := func() *TestOnlyFromDBField { var record TestOnlyFromDBField - has, err := testEngine.Where("id = ?", 1).Get(&record) + has, err := testEngine.Where("`id` = ?", 1).Get(&record) assert.NoError(t, err) assert.EqualValues(t, true, has) assert.EqualValues(t, "", record.OnlyFromDBField) diff --git a/integrations/tags_test.go b/integrations/tags_test.go index fc7b505e..b5bf222e 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -458,7 +458,7 @@ func TestExtends5(t *testing.T) { list := make([]Book, 0) err = session. Select(fmt.Sprintf( - "%s.%s, sc.%s AS %s, sc.%s AS %s, s.%s, s.%s", + "%s.%s, `sc`.%s AS %s, `sc`.%s AS %s, `s`.%s, `s`.%s", quote(bookTableName), quote("id"), quote("Width"), @@ -472,12 +472,12 @@ func TestExtends5(t *testing.T) { Join( "LEFT", sizeTableName+" AS `sc`", - bookTableName+".`SizeClosed`=sc.`id`", + bookTableName+".`SizeClosed`=`sc`.`id`", ). Join( "LEFT", sizeTableName+" AS `s`", - bookTableName+".`Size`=s.`id`", + bookTableName+".`Size`=`s`.`id`", ). Find(&list) assert.NoError(t, err) @@ -730,7 +730,7 @@ func TestLowerCase(t *testing.T) { err := testEngine.Sync2(&Lowercase{}) assert.NoError(t, err) - _, err = testEngine.Where("id > 0").Delete(&Lowercase{}) + _, err = testEngine.Where("`id` > 0").Delete(&Lowercase{}) assert.NoError(t, err) _, err = testEngine.Insert(&Lowercase{ended: 1}) diff --git a/integrations/time_test.go b/integrations/time_test.go index cd2e879f..a8447eea 100644 --- a/integrations/time_test.go +++ b/integrations/time_test.go @@ -324,7 +324,7 @@ func TestTimeUserDeleted(t *testing.T) { fmt.Println("user2 str", user2.CreatedAtStr, user2.UpdatedAtStr) var user3 UserDeleted - cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + cnt, err = testEngine.Where("`id` = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) assert.True(t, !utils.IsTimeZero(user3.DeletedAt)) @@ -386,7 +386,7 @@ func TestTimeUserDeletedDiffLoc(t *testing.T) { fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) var user3 UserDeleted2 - cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + cnt, err = testEngine.Where("`id` = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) assert.True(t, !utils.IsTimeZero(user3.DeletedAt)) @@ -457,7 +457,7 @@ func TestCustomTimeUserDeleted(t *testing.T) { fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) var user3 UserDeleted3 - cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + cnt, err = testEngine.Where("`id` = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) assert.True(t, !utils.IsTimeZero(time.Time(user3.DeletedAt))) @@ -519,7 +519,7 @@ func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) var user3 UserDeleted4 - cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + cnt, err = testEngine.Where("`id` = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) assert.True(t, !utils.IsTimeZero(time.Time(user3.DeletedAt))) diff --git a/integrations/types_null_test.go b/integrations/types_null_test.go index 86ce1939..8d98b456 100644 --- a/integrations/types_null_test.go +++ b/integrations/types_null_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" ) -type NullType struct { +type NullStruct struct { Id int `xorm:"pk autoincr"` Name sql.NullString Age sql.NullInt64 @@ -65,26 +65,26 @@ func (m CustomStruct) Value() (driver.Value, error) { func TestCreateNullStructTable(t *testing.T) { assert.NoError(t, PrepareEngine()) - err := testEngine.CreateTables(new(NullType)) + err := testEngine.CreateTables(new(NullStruct)) assert.NoError(t, err) } func TestDropNullStructTable(t *testing.T) { assert.NoError(t, PrepareEngine()) - err := testEngine.DropTables(new(NullType)) + err := testEngine.DropTables(new(NullStruct)) assert.NoError(t, err) } func TestNullStructInsert(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) - item1 := new(NullType) + item1 := new(NullStruct) _, err := testEngine.Insert(item1) assert.NoError(t, err) assert.EqualValues(t, 1, item1.Id) - item := NullType{ + item := NullStruct{ Name: sql.NullString{String: "haolei", Valid: true}, Age: sql.NullInt64{Int64: 34, Valid: true}, Height: sql.NullFloat64{Float64: 1.72, Valid: true}, @@ -95,9 +95,9 @@ func TestNullStructInsert(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, item.Id) - items := []NullType{} + items := []NullStruct{} for i := 0; i < 5; i++ { - item := NullType{ + item := NullStruct{ Name: sql.NullString{String: "haolei_" + fmt.Sprint(i+1), Valid: true}, Age: sql.NullInt64{Int64: 30 + int64(i), Valid: true}, Height: sql.NullFloat64{Float64: 1.5 + 1.1*float64(i), Valid: true}, @@ -111,7 +111,7 @@ func TestNullStructInsert(t *testing.T) { _, err = testEngine.Insert(&items) assert.NoError(t, err) - items = make([]NullType, 0, 7) + items = make([]NullStruct, 0, 7) err = testEngine.Find(&items) assert.NoError(t, err) assert.EqualValues(t, 7, len(items)) @@ -119,9 +119,9 @@ func TestNullStructInsert(t *testing.T) { func TestNullStructUpdate(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) - _, err := testEngine.Insert([]NullType{ + _, err := testEngine.Insert([]NullStruct{ { Name: sql.NullString{ String: "name1", @@ -150,7 +150,7 @@ func TestNullStructUpdate(t *testing.T) { assert.NoError(t, err) if true { // 测试可插入NULL - item := new(NullType) + item := new(NullStruct) item.Age = sql.NullInt64{Int64: 23, Valid: true} item.Height = sql.NullFloat64{Float64: 0, Valid: false} // update to NULL @@ -160,7 +160,7 @@ func TestNullStructUpdate(t *testing.T) { } if true { // 测试In update - item := new(NullType) + item := new(NullStruct) item.Age = sql.NullInt64{Int64: 23, Valid: true} affected, err := testEngine.In("id", 3, 4).Cols("age", "height", "is_man").Update(item) assert.NoError(t, err) @@ -168,17 +168,17 @@ func TestNullStructUpdate(t *testing.T) { } if true { // 测试where - item := new(NullType) + item := new(NullStruct) item.Name = sql.NullString{String: "nullname", Valid: true} item.IsMan = sql.NullBool{Bool: true, Valid: true} item.Age = sql.NullInt64{Int64: 34, Valid: true} - _, err := testEngine.Where("age > ?", 34).Update(item) + _, err := testEngine.Where("`age` > ?", 34).Update(item) assert.NoError(t, err) } if true { // 修改全部时,插入空值 - item := &NullType{ + item := &NullStruct{ Name: sql.NullString{String: "winxxp", Valid: true}, Age: sql.NullInt64{Int64: 30, Valid: true}, Height: sql.NullFloat64{Float64: 1.72, Valid: true}, @@ -192,9 +192,9 @@ func TestNullStructUpdate(t *testing.T) { func TestNullStructFind(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) - _, err := testEngine.Insert([]NullType{ + _, err := testEngine.Insert([]NullStruct{ { Name: sql.NullString{ String: "name1", @@ -223,7 +223,7 @@ func TestNullStructFind(t *testing.T) { assert.NoError(t, err) if true { - item := new(NullType) + item := new(NullStruct) has, err := testEngine.ID(1).Get(item) assert.NoError(t, err) assert.True(t, has) @@ -235,7 +235,7 @@ func TestNullStructFind(t *testing.T) { } if true { - item := new(NullType) + item := new(NullStruct) item.Id = 2 has, err := testEngine.Get(item) assert.NoError(t, err) @@ -243,13 +243,13 @@ func TestNullStructFind(t *testing.T) { } if true { - item := make([]NullType, 0) + item := make([]NullStruct, 0) err := testEngine.ID(2).Find(&item) assert.NoError(t, err) } if true { - item := make([]NullType, 0) + item := make([]NullStruct, 0) err := testEngine.Asc("age").Find(&item) assert.NoError(t, err) } @@ -257,12 +257,12 @@ func TestNullStructFind(t *testing.T) { func TestNullStructIterate(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) if true { - err := testEngine.Where("age IS NOT NULL").OrderBy("age").Iterate(new(NullType), + err := testEngine.Where("`age` IS NOT NULL").OrderBy("age").Iterate(new(NullStruct), func(i int, bean interface{}) error { - nultype := bean.(*NullType) + nultype := bean.(*NullStruct) fmt.Println(i, nultype) return nil }) @@ -272,21 +272,21 @@ func TestNullStructIterate(t *testing.T) { func TestNullStructCount(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) if true { - item := new(NullType) - _, err := testEngine.Where("age IS NOT NULL").Count(item) + item := new(NullStruct) + _, err := testEngine.Where("`age` IS NOT NULL").Count(item) assert.NoError(t, err) } } func TestNullStructRows(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) - item := new(NullType) - rows, err := testEngine.Where("id > ?", 1).Rows(item) + item := new(NullStruct) + rows, err := testEngine.Where("`id` > ?", 1).Rows(item) assert.NoError(t, err) defer rows.Close() @@ -298,13 +298,13 @@ func TestNullStructRows(t *testing.T) { func TestNullStructDelete(t *testing.T) { assert.NoError(t, PrepareEngine()) - assertSync(t, new(NullType)) + assertSync(t, new(NullStruct)) - item := new(NullType) + item := new(NullStruct) _, err := testEngine.ID(1).Delete(item) assert.NoError(t, err) - _, err = testEngine.Where("id > ?", 1).Delete(item) + _, err = testEngine.Where("`id` > ?", 1).Delete(item) assert.NoError(t, err) } diff --git a/integrations/types_test.go b/integrations/types_test.go index b08dfec5..48facb21 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -428,7 +428,7 @@ func TestUnsignedUint64(t *testing.T) { assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) case schemas.MYSQL: assert.EqualValues(t, "UNSIGNED BIGINT", tables[0].Columns()[0].SQLType.Name) - case schemas.POSTGRES: + case schemas.POSTGRES, schemas.DAMENG: assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) case schemas.MSSQL: assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) @@ -472,9 +472,7 @@ func TestUnsignedUint32(t *testing.T) { assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) case schemas.MYSQL: assert.EqualValues(t, "UNSIGNED INT", tables[0].Columns()[0].SQLType.Name) - case schemas.POSTGRES: - assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) - case schemas.MSSQL: + case schemas.POSTGRES, schemas.MSSQL, schemas.DAMENG: assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) default: assert.False(t, true, "Unsigned is not implemented") @@ -507,7 +505,7 @@ func TestUnsignedTinyInt(t *testing.T) { assert.EqualValues(t, 1, len(tables[0].Columns())) switch testEngine.Dialect().URI().DBType { - case schemas.SQLITE: + case schemas.SQLITE, schemas.DAMENG: assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) case schemas.MYSQL: assert.EqualValues(t, "UNSIGNED TINYINT", tables[0].Columns()[0].SQLType.Name) @@ -516,7 +514,7 @@ func TestUnsignedTinyInt(t *testing.T) { case schemas.MSSQL: assert.EqualValues(t, "INT", tables[0].Columns()[0].SQLType.Name) default: - assert.False(t, true, "Unsigned is not implemented") + assert.False(t, true, fmt.Sprintf("Unsigned is not implemented, returned %s", tables[0].Columns()[0].SQLType.Name)) } cnt, err := testEngine.Insert(&MyUnsignedTinyIntStruct{ diff --git a/internal/statements/insert.go b/internal/statements/insert.go index 84547cdf..91a33319 100644 --- a/internal/statements/insert.go +++ b/internal/statements/insert.go @@ -10,6 +10,7 @@ import ( "strings" "xorm.io/builder" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -42,7 +43,19 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } - if len(colNames) <= 0 { + var hasInsertColumns = len(colNames) > 0 + var needSeq = len(table.AutoIncrement) > 0 && (statement.dialect.URI().DBType == schemas.ORACLE || statement.dialect.URI().DBType == schemas.DAMENG) + if needSeq { + for _, col := range colNames { + if strings.EqualFold(col, table.AutoIncrement) { + needSeq = false + break + } + } + } + + if !hasInsertColumns && statement.dialect.URI().DBType != schemas.ORACLE && + statement.dialect.URI().DBType != schemas.DAMENG { if statement.dialect.URI().DBType == schemas.MYSQL { if _, err := buf.WriteString(" VALUES ()"); err != nil { return "", nil, err @@ -60,6 +73,10 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } + if needSeq { + colNames = append(colNames, table.AutoIncrement) + } + if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames()...), ","); err != nil { return "", nil, err } @@ -80,13 +97,23 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } + if needSeq { + if len(args) > 0 { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + } + if _, err := buf.WriteString(utils.SeqName(tableName) + ".nextval"); err != nil { + return "", nil, err + } + } if len(exprs) > 0 { if _, err := buf.WriteString(","); err != nil { return "", nil, err } - } - if err := exprs.WriteArgs(buf); err != nil { - return "", nil, err + if err := exprs.WriteArgs(buf); err != nil { + return "", nil, err + } } if _, err := buf.WriteString(" FROM "); err != nil { @@ -113,6 +140,18 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } + // Insert tablename (id) Values(seq_tablename.nextval) + if needSeq { + if hasInsertColumns { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + } + if _, err := buf.WriteString(utils.SeqName(tableName) + ".nextval"); err != nil { + return "", nil, err + } + } + if len(exprs) > 0 { if _, err := buf.WriteString(","); err != nil { return "", nil, err diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 1fcc0bba..b85773db 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -539,7 +539,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) aliasName = schemas.CommonQuoter.Trim(aliasName) - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), aliasName, statement.ReplaceQuote(condition)) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) case *builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() @@ -552,7 +552,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) aliasName = schemas.CommonQuoter.Trim(aliasName) - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), aliasName, statement.ReplaceQuote(condition)) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) default: tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) @@ -560,6 +560,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition var buf strings.Builder statement.dialect.Quoter().QuoteTo(&buf, tbName) tbName = buf.String() + } else { + tbName = statement.ReplaceQuote(tbName) } fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) } @@ -642,14 +644,6 @@ func (statement *Statement) genColumnStr() string { return buf.String() } -// GenCreateTableSQL generated create table SQL -func (statement *Statement) GenCreateTableSQL() []string { - statement.RefTable.StoreEngine = statement.StoreEngine - statement.RefTable.Charset = statement.Charset - s, _ := statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName()) - return s -} - // GenIndexSQL generated create index SQL func (statement *Statement) GenIndexSQL() []string { var sqls []string diff --git a/internal/utils/name.go b/internal/utils/name.go index 840dd9e8..aeef683d 100644 --- a/internal/utils/name.go +++ b/internal/utils/name.go @@ -6,9 +6,15 @@ package utils import ( "fmt" + "strings" ) // IndexName returns index name func IndexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } + +// SeqName returns sequence name for some table +func SeqName(tableName string) string { + return "SEQ_" + strings.ToUpper(tableName) +} diff --git a/internal/utils/slice.go b/internal/utils/slice.go index 89685706..06a1a006 100644 --- a/internal/utils/slice.go +++ b/internal/utils/slice.go @@ -11,8 +11,8 @@ func SliceEq(left, right []string) bool { if len(left) != len(right) { return false } - sort.Sort(sort.StringSlice(left)) - sort.Sort(sort.StringSlice(right)) + sort.Strings(left) + sort.Strings(right) for i := 0; i < len(left); i++ { if left[i] != right[i] { return false @@ -20,3 +20,12 @@ func SliceEq(left, right []string) bool { } return true } + +func IndexSlice(s []string, c string) int { + for i, ss := range s { + if c == ss { + return i + } + } + return -1 +} diff --git a/scan.go b/scan.go index 56d3c9d6..2788453e 100644 --- a/scan.go +++ b/scan.go @@ -136,7 +136,10 @@ func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, field scanResults[i] = &s } - if err := rows.Scan(scanResults...); err != nil { + if err := engine.driver.Scan(&dialects.ScanContext{ + DBLocation: engine.DatabaseTZ, + UserLocation: engine.TZLocation, + }, rows, types, scanResults...); err != nil { return nil, err } diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 8e351dc0..061a6ea2 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -176,6 +176,10 @@ func TestReplace(t *testing.T) { "UPDATE table SET `a` = ~ `a`, `b`='abc`'", "UPDATE table SET [a] = ~ [a], [b]='abc`'", }, + { + "INSERT INTO `insert_where` (`height`,`name`,`repo_id`,`width`,`index`) SELECT $1,$2,$3,$4,coalesce(MAX(`index`),0)+1 FROM `insert_where` WHERE (`repo_id`=$5)", + "INSERT INTO [insert_where] ([height],[name],[repo_id],[width],[index]) SELECT $1,$2,$3,$4,coalesce(MAX([index]),0)+1 FROM [insert_where] WHERE ([repo_id]=$5)", + }, } for _, kase := range kases { diff --git a/schemas/type.go b/schemas/type.go index cf730134..d192bac6 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -22,6 +22,7 @@ const ( MYSQL DBType = "mysql" MSSQL DBType = "mssql" ORACLE DBType = "oracle" + DAMENG DBType = "dameng" ) // SQLType represents SQL types @@ -105,12 +106,14 @@ var ( Integer = "INTEGER" BigInt = "BIGINT" UnsignedBigInt = "UNSIGNED BIGINT" + Number = "NUMBER" Enum = "ENUM" Set = "SET" Char = "CHAR" Varchar = "VARCHAR" + VARCHAR2 = "VARCHAR2" NChar = "NCHAR" NVarchar = "NVARCHAR" TinyText = "TINYTEXT" @@ -174,6 +177,7 @@ var ( Integer: NUMERIC_TYPE, BigInt: NUMERIC_TYPE, UnsignedBigInt: NUMERIC_TYPE, + Number: NUMERIC_TYPE, Enum: TEXT_TYPE, Set: TEXT_TYPE, @@ -185,6 +189,7 @@ var ( Char: TEXT_TYPE, NChar: TEXT_TYPE, Varchar: TEXT_TYPE, + VARCHAR2: TEXT_TYPE, NVarchar: TEXT_TYPE, TinyText: TEXT_TYPE, Text: TEXT_TYPE, diff --git a/session.go b/session.go index 499b7df4..a96d2fc9 100644 --- a/session.go +++ b/session.go @@ -524,6 +524,9 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec if !ok { return fmt.Errorf("cannot convert %#v as bytes", scanResult) } + if data == nil { + return nil + } return structConvert.FromDB(data) } } diff --git a/session_get.go b/session_get.go index 08172524..22b116a9 100644 --- a/session_get.go +++ b/session_get.go @@ -130,9 +130,6 @@ var ( valuerTypePlaceHolder driver.Valuer valuerType = reflect.TypeOf(&valuerTypePlaceHolder).Elem() - scannerTypePlaceHolder sql.Scanner - scannerType = reflect.TypeOf(&scannerTypePlaceHolder).Elem() - conversionTypePlaceHolder convert.Conversion conversionType = reflect.TypeOf(&conversionTypePlaceHolder).Elem() ) diff --git a/session_insert.go b/session_insert.go index a8f365c7..43a4118b 100644 --- a/session_insert.go +++ b/session_insert.go @@ -123,6 +123,12 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e } fieldValue := *ptrFieldValue if col.IsAutoIncrement && utils.IsZero(fieldValue.Interface()) { + if session.engine.dialect.Features().AutoincrMode == dialects.SequenceAutoincrMode { + if i == 0 { + colNames = append(colNames, col.Name) + } + colPlaces = append(colPlaces, utils.SeqName(tableName)+".nextval") + } continue } if col.MapType == schemas.ONLYFROMDB { @@ -277,6 +283,7 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { if err != nil { return 0, err } + sqlStr = session.engine.dialect.Quoter().Replace(sqlStr) handleAfterInsertProcessorFunc := func(bean interface{}) { if session.isAutoCommit { @@ -307,16 +314,49 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { // if there is auto increment column and driver don't support return it if len(table.AutoIncrement) > 0 && !session.engine.driver.Features().SupportReturnInsertedID { - var sql = sqlStr - if session.engine.dialect.URI().DBType == schemas.ORACLE { - sql = "select seq_atable.currval from dual" + var sql string + var newArgs []interface{} + var needCommit bool + var id int64 + if session.engine.dialect.URI().DBType == schemas.ORACLE || session.engine.dialect.URI().DBType == schemas.DAMENG { + if session.isAutoCommit { // if it's not in transaction + if err := session.Begin(); err != nil { + return 0, err + } + needCommit = true + } + _, err := session.exec(sqlStr, args...) + if err != nil { + return 0, err + } + i := utils.IndexSlice(colNames, table.AutoIncrement) + if i > -1 { + id, err = convert.AsInt64(args[i]) + if err != nil { + return 0, err + } + } else { + sql = fmt.Sprintf("select %s.currval from dual", utils.SeqName(tableName)) + } + } else { + sql = sqlStr + newArgs = args } - rows, err := session.queryRows(sql, args...) - if err != nil { - return 0, err + if id == 0 { + err := session.queryRow(sql, newArgs...).Scan(&id) + if err != nil { + return 0, err + } + if needCommit { + if err := session.Commit(); err != nil { + return 0, err + } + } + if id == 0 { + return 0, errors.New("insert successfully but not returned id") + } } - defer rows.Close() defer handleAfterInsertProcessorFunc(bean) @@ -331,16 +371,6 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { } } - var id int64 - if !rows.Next() { - if rows.Err() != nil { - return 0, rows.Err() - } - return 0, errors.New("insert successfully but not returned id") - } - if err := rows.Scan(&id); err != nil { - return 1, err - } aiValue, err := table.AutoIncrColumn().ValueOf(bean) if err != nil { session.engine.logger.Errorf("%v", err) @@ -628,6 +658,7 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, if err != nil { return 0, err } + sql = session.engine.dialect.Quoter().Replace(sql) if err := session.cacheInsert(tableName); err != nil { return 0, err @@ -654,6 +685,7 @@ func (session *Session) insertMultipleMap(columns []string, argss [][]interface{ if err != nil { return 0, err } + sql = session.engine.dialect.Quoter().Replace(sql) if err := session.cacheInsert(tableName); err != nil { return 0, err diff --git a/session_schema.go b/session_schema.go index 2e64350f..73352135 100644 --- a/session_schema.go +++ b/session_schema.go @@ -6,12 +6,14 @@ package xorm import ( "bufio" + "context" "database/sql" "fmt" "io" "os" "strings" + "xorm.io/xorm/dialects" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -40,13 +42,28 @@ func (session *Session) createTable(bean interface{}) error { return err } - sqlStrs := session.statement.GenCreateTableSQL() - for _, s := range sqlStrs { - _, err := session.exec(s) + session.statement.RefTable.StoreEngine = session.statement.StoreEngine + session.statement.RefTable.Charset = session.statement.Charset + tableName := session.statement.TableName() + refTable := session.statement.RefTable + if refTable.AutoIncrement != "" && session.engine.dialect.Features().AutoincrMode == dialects.SequenceAutoincrMode { + sqlStr, err := session.engine.dialect.CreateSequenceSQL(context.Background(), session.engine.db, utils.SeqName(tableName)) if err != nil { return err } + if _, err := session.exec(sqlStr); err != nil { + return err + } } + + sqlStr, _, err := session.engine.dialect.CreateTableSQL(context.Background(), session.engine.db, refTable, tableName) + if err != nil { + return err + } + if _, err := session.exec(sqlStr); err != nil { + return err + } + return nil } @@ -141,11 +158,32 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { checkIfExist = exist } - if checkIfExist { - _, err := session.exec(sqlStr) + if !checkIfExist { + return nil + } + if _, err := session.exec(sqlStr); err != nil { return err } - return nil + + if session.engine.dialect.Features().AutoincrMode == dialects.IncrAutoincrMode { + return nil + } + + var seqName = utils.SeqName(tableName) + exist, err := session.engine.dialect.IsSequenceExist(session.ctx, session.getQueryer(), seqName) + if err != nil { + return err + } + if !exist { + return nil + } + + sqlStr, err = session.engine.dialect.DropSequenceSQL(seqName) + if err != nil { + return err + } + _, err = session.exec(sqlStr) + return err } // IsTableExist if a table is exist diff --git a/session_update.go b/session_update.go index 7d91346e..4fd45a53 100644 --- a/session_update.go +++ b/session_update.go @@ -278,7 +278,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 condBeanIsStruct := false if len(condiBean) > 0 { if c, ok := condiBean[0].(map[string]interface{}); ok { - autoCond = builder.Eq(c) + var eq = make(builder.Eq) + for k, v := range c { + eq[session.engine.Quote(k)] = v + } + autoCond = builder.Eq(eq) } else { ct := reflect.TypeOf(condiBean[0]) k := ct.Kind() From d92fb412ee645ffe4a101995c163486c67184d3d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Aug 2021 15:42:34 +0800 Subject: [PATCH 120/179] Make Get and Rows.Scan accept multiple parameters (#2029) Now the below behaviours are allowed. ```Go var id int64 var name string has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) // SELECT id, name FROM user LIMIT 1 ``` ```Go rows, err := engine.Cols("name", "age").Rows(&User{Name:name}) // SELECT * FROM user defer rows.Close() for rows.Next() { var name string var age int err = rows.Scan(&name, &age) } ``` Reviewed-on: https://gitea.com/xorm/xorm/pulls/2029 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- README.md | 22 ++++++ README_CN.md | 22 ++++++ doc.go | 16 +++++ engine.go | 4 +- integrations/rows_test.go | 44 ++++++++++++ integrations/session_get_test.go | 25 +++++++ interface.go | 2 +- rows.go | 29 ++++---- session_get.go | 117 ++++++++++++++++--------------- 9 files changed, 209 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index f4bee6b6..4cb4ac13 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,11 @@ has, err := engine.Table(&user).Where("name = ?", name).Cols("id").Get(&id) has, err := engine.SQL("select id from user").Get(&id) // SELECT id FROM user WHERE name = ? +var id int64 +var name string +has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) +// SELECT id, name FROM user LIMIT 1 + var valuesMap = make(map[string]string) has, err := engine.Table(&user).Where("id = ?", id).Get(&valuesMap) // SELECT * FROM user WHERE id = ? @@ -234,7 +239,11 @@ err := engine.BufferSize(100).Iterate(&User{Name:name}, func(idx int, bean inter }) // SELECT * FROM user Limit 0, 100 // SELECT * FROM user Limit 101, 100 +``` +You can use rows which is similiar with `sql.Rows` + +```Go rows, err := engine.Rows(&User{Name:name}) // SELECT * FROM user defer rows.Close() @@ -244,6 +253,19 @@ for rows.Next() { } ``` +or + +```Go +rows, err := engine.Cols("name", "age").Rows(&User{Name:name}) +// SELECT * FROM user +defer rows.Close() +for rows.Next() { + var name string + var age int + err = rows.Scan(&name, &age) +} +``` + * `Update` update one or more records, default will update non-empty and non-zero fields except when you use Cols, AllCols and so on. ```Go diff --git a/README_CN.md b/README_CN.md index 500bb1fb..c87aa079 100644 --- a/README_CN.md +++ b/README_CN.md @@ -158,6 +158,11 @@ has, err := engine.Table(&user).Where("name = ?", name).Cols("id").Get(&id) has, err := engine.SQL("select id from user").Get(&id) // SELECT id FROM user WHERE name = ? +var id int64 +var name string +has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) +// SELECT id, name FROM user LIMIT 1 + var valuesMap = make(map[string]string) has, err := engine.Table(&user).Where("id = ?", id).Get(&valuesMap) // SELECT * FROM user WHERE id = ? @@ -231,7 +236,11 @@ err := engine.BufferSize(100).Iterate(&User{Name:name}, func(idx int, bean inter }) // SELECT * FROM user Limit 0, 100 // SELECT * FROM user Limit 101, 100 +``` +Rows 的用法类似 `sql.Rows`。 + +```Go rows, err := engine.Rows(&User{Name:name}) // SELECT * FROM user defer rows.Close() @@ -241,6 +250,19 @@ for rows.Next() { } ``` +或者 + +```Go +rows, err := engine.Cols("name", "age").Rows(&User{Name:name}) +// SELECT * FROM user +defer rows.Close() +for rows.Next() { + var name string + var age int + err = rows.Scan(&name, &age) +} +``` + * `Update` 更新数据,除非使用Cols,AllCols函数指明,默认只更新非空和非0的字段 ```Go diff --git a/doc.go b/doc.go index d0653232..a1565806 100644 --- a/doc.go +++ b/doc.go @@ -67,6 +67,11 @@ There are 8 major ORM methods and many helpful methods to use to operate databas has, err := engine.Table("user").Where("name = ?", name).Get(&id) // SELECT id FROM user WHERE name = ? LIMIT 1 + var id int64 + var name string + has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) + // SELECT id, name FROM user LIMIT 1 + 3. Query multiple records from database var sliceOfStructs []Struct @@ -97,6 +102,17 @@ another is Rows err = rows.Scan(bean) } +or + + rows, err := engine.Cols("name", "age").Rows(...) + // SELECT * FROM user + defer rows.Close() + for rows.Next() { + var name string + var age int + err = rows.Scan(&name, &age) + } + 5. Update one or more records affected, err := engine.ID(...).Update(&user) diff --git a/engine.go b/engine.go index 3681ff3c..7d72dea3 100644 --- a/engine.go +++ b/engine.go @@ -1135,10 +1135,10 @@ func (engine *Engine) Delete(beans ...interface{}) (int64, error) { // Get retrieve one record from table, bean's non-empty fields // are conditions -func (engine *Engine) Get(bean interface{}) (bool, error) { +func (engine *Engine) Get(beans ...interface{}) (bool, error) { session := engine.NewSession() defer session.Close() - return session.Get(bean) + return session.Get(beans...) } // Exist returns true if the record exist otherwise return false diff --git a/integrations/rows_test.go b/integrations/rows_test.go index f68030a4..a5648675 100644 --- a/integrations/rows_test.go +++ b/integrations/rows_test.go @@ -160,5 +160,49 @@ func TestRowsSpecTableName(t *testing.T) { assert.NoError(t, err) cnt++ } + assert.NoError(t, rows.Err()) assert.EqualValues(t, 1, cnt) } + +func TestRowsScanVars(t *testing.T) { + type RowsScanVars struct { + Id int64 + Name string + Age int + } + + assert.NoError(t, PrepareEngine()) + assert.NoError(t, testEngine.Sync2(new(RowsScanVars))) + + cnt, err := testEngine.Insert(&RowsScanVars{ + Name: "xlw", + Age: 42, + }, &RowsScanVars{ + Name: "xlw2", + Age: 24, + }) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + + rows, err := testEngine.Cols("name", "age").Rows(new(RowsScanVars)) + assert.NoError(t, err) + defer rows.Close() + + cnt = 0 + for rows.Next() { + var name string + var age int + err = rows.Scan(&name, &age) + assert.NoError(t, err) + if cnt == 0 { + assert.EqualValues(t, "xlw", name) + assert.EqualValues(t, 42, age) + } else if cnt == 1 { + assert.EqualValues(t, "xlw2", name) + assert.EqualValues(t, 24, age) + } + cnt++ + } + assert.NoError(t, rows.Err()) + assert.EqualValues(t, 2, cnt) +} diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 4fc30adb..6f4c1dbe 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -890,3 +890,28 @@ func TestGetTime(t *testing.T) { assert.True(t, has) assert.EqualValues(t, gts.CreateTime.Format(time.RFC3339), gn.Format(time.RFC3339)) } + +func TestGetVars(t *testing.T) { + type GetVars struct { + Id int64 + Name string + Age int + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetVars)) + + _, err := testEngine.Insert(&GetVars{ + Name: "xlw", + Age: 42, + }) + assert.NoError(t, err) + + var name string + var age int + has, err := testEngine.Table(new(GetVars)).Cols("name", "age").Get(&name, &age) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "xlw", name) + assert.EqualValues(t, 42, age) +} diff --git a/interface.go b/interface.go index 5d68f536..42dc9a0a 100644 --- a/interface.go +++ b/interface.go @@ -37,7 +37,7 @@ type Interface interface { Exist(bean ...interface{}) (bool, error) Find(interface{}, ...interface{}) error FindAndCount(interface{}, ...interface{}) (int64, error) - Get(interface{}) (bool, error) + Get(...interface{}) (bool, error) GroupBy(keys string) *Session ID(interface{}) *Session In(string, ...interface{}) *Session diff --git a/rows.go b/rows.go index 8e7cc075..76fc1e90 100644 --- a/rows.go +++ b/rows.go @@ -11,7 +11,6 @@ import ( "xorm.io/builder" "xorm.io/xorm/core" - "xorm.io/xorm/internal/utils" ) // Rows rows wrapper a rows to @@ -93,17 +92,26 @@ func (rows *Rows) Err() error { } // Scan row record to bean properties -func (rows *Rows) Scan(bean interface{}) error { +func (rows *Rows) Scan(beans ...interface{}) error { if rows.Err() != nil { return rows.Err() } - if reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType { - return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) + var bean = beans[0] + var tp = reflect.TypeOf(bean) + if tp.Kind() == reflect.Ptr { + tp = tp.Elem() } + var beanKind = tp.Kind() - if err := rows.session.statement.SetRefBean(bean); err != nil { - return err + if len(beans) == 1 { + if reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType { + return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) + } + + if err := rows.session.statement.SetRefBean(bean); err != nil { + return err + } } fields, err := rows.rows.Columns() @@ -115,14 +123,7 @@ func (rows *Rows) Scan(bean interface{}) error { return err } - scanResults, err := rows.session.row2Slice(rows.rows, fields, types, bean) - if err != nil { - return err - } - - dataStruct := utils.ReflectValue(bean) - _, err = rows.session.slice2Bean(scanResults, fields, bean, &dataStruct, rows.session.statement.RefTable) - if err != nil { + if err := rows.session.scan(rows.rows, rows.session.statement.RefTable, beanKind, beans, types, fields); err != nil { return err } diff --git a/session_get.go b/session_get.go index 22b116a9..a82cae92 100644 --- a/session_get.go +++ b/session_get.go @@ -28,11 +28,11 @@ var ( // Get retrieve one record from database, bean's non-empty fields // will be as conditions -func (session *Session) Get(bean interface{}) (bool, error) { +func (session *Session) Get(beans ...interface{}) (bool, error) { if session.isAutoClose { defer session.Close() } - return session.get(bean) + return session.get(beans...) } func isPtrOfTime(v interface{}) bool { @@ -48,14 +48,17 @@ func isPtrOfTime(v interface{}) bool { return el.Type().ConvertibleTo(schemas.TimeType) } -func (session *Session) get(bean interface{}) (bool, error) { +func (session *Session) get(beans ...interface{}) (bool, error) { defer session.resetStatement() if session.statement.LastError != nil { return false, session.statement.LastError } + if len(beans) == 0 { + return false, errors.New("needs at least one parameter for get") + } - beanValue := reflect.ValueOf(bean) + beanValue := reflect.ValueOf(beans[0]) if beanValue.Kind() != reflect.Ptr { return false, errors.New("needs a pointer to a value") } else if beanValue.Elem().Kind() == reflect.Ptr { @@ -64,8 +67,9 @@ func (session *Session) get(bean interface{}) (bool, error) { return false, ErrObjectIsNil } - if beanValue.Elem().Kind() == reflect.Struct && !isPtrOfTime(bean) { - if err := session.statement.SetRefBean(bean); err != nil { + var isStruct = beanValue.Elem().Kind() == reflect.Struct && !isPtrOfTime(beans[0]) + if isStruct { + if err := session.statement.SetRefBean(beans[0]); err != nil { return false, err } } @@ -79,7 +83,7 @@ func (session *Session) get(bean interface{}) (bool, error) { return false, ErrTableNotFound } session.statement.Limit(1) - sqlStr, args, err = session.statement.GenGetSQL(bean) + sqlStr, args, err = session.statement.GenGetSQL(beans[0]) if err != nil { return false, err } @@ -90,10 +94,10 @@ func (session *Session) get(bean interface{}) (bool, error) { table := session.statement.RefTable - if session.statement.ColumnMap.IsEmpty() && session.canCache() && beanValue.Elem().Kind() == reflect.Struct { + if session.statement.ColumnMap.IsEmpty() && session.canCache() && isStruct { if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && !session.statement.GetUnscoped() { - has, err := session.cacheGet(bean, sqlStr, args...) + has, err := session.cacheGet(beans[0], sqlStr, args...) if err != ErrCacheFailed { return has, err } @@ -101,12 +105,12 @@ func (session *Session) get(bean interface{}) (bool, error) { } context := session.statement.Context - if context != nil { + if context != nil && isStruct { res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args)) if res != nil { session.engine.logger.Debugf("hit context cache: %s", sqlStr) - structValue := reflect.Indirect(reflect.ValueOf(bean)) + structValue := reflect.Indirect(reflect.ValueOf(beans[0])) structValue.Set(reflect.Indirect(reflect.ValueOf(res))) session.lastSQL = "" session.lastSQLArgs = nil @@ -114,13 +118,13 @@ func (session *Session) get(bean interface{}) (bool, error) { } } - has, err := session.nocacheGet(beanValue.Elem().Kind(), table, bean, sqlStr, args...) + has, err := session.nocacheGet(beanValue.Elem().Kind(), table, beans, sqlStr, args...) if err != nil || !has { return has, err } - if context != nil { - context.Put(fmt.Sprintf("%v-%v", sqlStr, args), bean) + if context != nil && isStruct { + context.Put(fmt.Sprintf("%v-%v", sqlStr, args), beans[0]) } return true, nil @@ -148,7 +152,7 @@ func isScannableStruct(bean interface{}, typeLen int) bool { return true } -func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { +func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, beans []interface{}, sqlStr string, args ...interface{}) (bool, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { return false, err @@ -168,27 +172,39 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, if err != nil { return true, err } - switch beanKind { - case reflect.Struct: - if !isScannableStruct(bean, len(types)) { - break - } - return session.getStruct(rows, types, fields, table, bean) - case reflect.Slice: - return session.getSlice(rows, types, fields, bean) - case reflect.Map: - return session.getMap(rows, types, fields, bean) - } - return session.getVars(rows, types, fields, bean) + return true, session.scan(rows, table, beanKind, beans, types, fields) } -func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) (bool, error) { +func (session *Session) scan(rows *core.Rows, table *schemas.Table, firstBeanKind reflect.Kind, beans []interface{}, types []*sql.ColumnType, fields []string) error { + if len(beans) == 1 { + bean := beans[0] + switch firstBeanKind { + case reflect.Struct: + if !isScannableStruct(bean, len(types)) { + break + } + return session.getStruct(rows, types, fields, table, bean) + case reflect.Slice: + return session.getSlice(rows, types, fields, bean) + case reflect.Map: + return session.getMap(rows, types, fields, bean) + } + } + + if len(beans) != len(types) { + return fmt.Errorf("expected columns %d, but only %d variables", len(types), len(beans)) + } + + return session.engine.scan(rows, fields, types, beans...) +} + +func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) error { switch t := bean.(type) { case *[]string: res, err := session.engine.scanStringInterface(rows, fields, types) if err != nil { - return true, err + return err } var needAppend = len(*t) == 0 // both support slice is empty or has been initlized @@ -199,17 +215,17 @@ func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, field (*t)[i] = r.(*sql.NullString).String } } - return true, nil + return nil case *[]interface{}: scanResults, err := session.engine.scanInterfaces(rows, fields, types) if err != nil { - return true, err + return err } var needAppend = len(*t) == 0 for ii := range fields { s, err := convert.Interface2Interface(session.engine.DatabaseTZ, scanResults[ii]) if err != nil { - return true, err + return err } if needAppend { *t = append(*t, s) @@ -217,54 +233,45 @@ func (session *Session) getSlice(rows *core.Rows, types []*sql.ColumnType, field (*t)[ii] = s } } - return true, nil + return nil default: - return true, fmt.Errorf("unspoorted slice type: %t", t) + return fmt.Errorf("unspoorted slice type: %t", t) } } -func (session *Session) getMap(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) (bool, error) { +func (session *Session) getMap(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) error { switch t := bean.(type) { case *map[string]string: scanResults, err := session.engine.scanStringInterface(rows, fields, types) if err != nil { - return true, err + return err } for ii, key := range fields { (*t)[key] = scanResults[ii].(*sql.NullString).String } - return true, nil + return nil case *map[string]interface{}: scanResults, err := session.engine.scanInterfaces(rows, fields, types) if err != nil { - return true, err + return err } for ii, key := range fields { s, err := convert.Interface2Interface(session.engine.DatabaseTZ, scanResults[ii]) if err != nil { - return true, err + return err } (*t)[key] = s } - return true, nil + return nil default: - return true, fmt.Errorf("unspoorted map type: %t", t) + return fmt.Errorf("unspoorted map type: %t", t) } } -func (session *Session) getVars(rows *core.Rows, types []*sql.ColumnType, fields []string, beans ...interface{}) (bool, error) { - if len(beans) != len(types) { - return false, fmt.Errorf("expected columns %d, but only %d variables", len(types), len(beans)) - } - - err := session.engine.scan(rows, fields, types, beans...) - return true, err -} - -func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) (bool, error) { +func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) error { scanResults, err := session.row2Slice(rows, fields, types, bean) if err != nil { - return false, err + return err } // close it before convert data rows.Close() @@ -272,10 +279,10 @@ func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fiel dataStruct := utils.ReflectValue(bean) _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table) if err != nil { - return true, err + return err } - return true, session.executeProcessors() + return session.executeProcessors() } func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) { @@ -354,7 +361,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf cacheBean := cacher.GetBean(tableName, sid) if cacheBean == nil { cacheBean = bean - has, err = session.nocacheGet(reflect.Struct, table, cacheBean, sqlStr, args...) + has, err = session.nocacheGet(reflect.Struct, table, []interface{}{cacheBean}, sqlStr, args...) if err != nil || !has { return has, err } From 0a429a241d245b442a6a6ddc1fc2817aff8e8d0c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Aug 2021 17:13:17 +0800 Subject: [PATCH 121/179] Drop sync function and rename sync2 to sync (#2018) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2018 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- README.md | 2 +- README_CN.md | 2 +- engine.go | 98 +--------------------------- integrations/cache_test.go | 6 +- integrations/engine_test.go | 4 +- integrations/performance_test.go | 6 +- integrations/processors_test.go | 4 +- integrations/rows_test.go | 6 +- integrations/session_cols_test.go | 4 +- integrations/session_cond_test.go | 4 +- integrations/session_count_test.go | 2 +- integrations/session_delete_test.go | 4 +- integrations/session_exist_test.go | 2 +- integrations/session_find_test.go | 6 +- integrations/session_get_test.go | 8 +-- integrations/session_insert_test.go | 40 ++++++------ integrations/session_iterate_test.go | 4 +- integrations/session_pk_test.go | 2 +- integrations/session_query_test.go | 20 +++--- integrations/session_raw_test.go | 4 +- integrations/session_schema_test.go | 38 +++++------ integrations/session_sum_test.go | 6 +- integrations/session_test.go | 2 +- integrations/session_update_test.go | 16 ++--- integrations/tags_test.go | 8 +-- integrations/types_test.go | 6 +- migrate/migrate_test.go | 8 +-- session_schema.go | 6 ++ 28 files changed, 116 insertions(+), 202 deletions(-) diff --git a/README.md b/README.md index 4cb4ac13..8c85938a 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ type User struct { Updated time.Time `xorm:"updated"` } -err := engine.Sync2(new(User)) +err := engine.Sync(new(User)) ``` * Create Engine Group diff --git a/README_CN.md b/README_CN.md index c87aa079..f2445019 100644 --- a/README_CN.md +++ b/README_CN.md @@ -84,7 +84,7 @@ type User struct { Updated time.Time `xorm:"updated"` } -err := engine.Sync2(new(User)) +err := engine.Sync(new(User)) ``` * 创建Engine组 diff --git a/engine.go b/engine.go index 7d72dea3..7a57b08a 100644 --- a/engine.go +++ b/engine.go @@ -7,7 +7,6 @@ package xorm import ( "context" "database/sql" - "errors" "fmt" "io" "os" @@ -925,104 +924,13 @@ func (engine *Engine) UnMapType(t reflect.Type) { func (engine *Engine) Sync(beans ...interface{}) error { session := engine.NewSession() defer session.Close() - - for _, bean := range beans { - v := utils.ReflectValue(bean) - tableNameNoSchema := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean) - table, err := engine.tagParser.ParseWithCache(v) - if err != nil { - return err - } - - isExist, err := session.Table(bean).isTableExist(tableNameNoSchema) - if err != nil { - return err - } - if !isExist { - err = session.createTable(bean) - if err != nil { - return err - } - } - /*isEmpty, err := engine.IsEmptyTable(bean) - if err != nil { - return err - }*/ - var isEmpty bool - if isEmpty { - err = session.dropTable(bean) - if err != nil { - return err - } - err = session.createTable(bean) - if err != nil { - return err - } - } else { - for _, col := range table.Columns() { - isExist, err := engine.dialect.IsColumnExist(engine.db, session.ctx, tableNameNoSchema, col.Name) - if err != nil { - return err - } - if !isExist { - if err := session.statement.SetRefBean(bean); err != nil { - return err - } - err = session.addColumn(col.Name) - if err != nil { - return err - } - } - } - - for name, index := range table.Indexes { - if err := session.statement.SetRefBean(bean); err != nil { - return err - } - if index.Type == schemas.UniqueType { - isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true) - if err != nil { - return err - } - if !isExist { - if err := session.statement.SetRefBean(bean); err != nil { - return err - } - - err = session.addUnique(tableNameNoSchema, name) - if err != nil { - return err - } - } - } else if index.Type == schemas.IndexType { - isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false) - if err != nil { - return err - } - if !isExist { - if err := session.statement.SetRefBean(bean); err != nil { - return err - } - - err = session.addIndex(tableNameNoSchema, name) - if err != nil { - return err - } - } - } else { - return errors.New("unknow index type") - } - } - } - } - return nil + return session.Sync(beans...) } // Sync2 synchronize structs to database tables +// Depricated func (engine *Engine) Sync2(beans ...interface{}) error { - s := engine.NewSession() - defer s.Close() - return s.Sync2(beans...) + return engine.Sync(beans...) } // CreateTables create tabls according bean diff --git a/integrations/cache_test.go b/integrations/cache_test.go index 6fdaa5cb..2caeaa34 100644 --- a/integrations/cache_test.go +++ b/integrations/cache_test.go @@ -26,7 +26,7 @@ func TestCacheFind(t *testing.T) { cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) - assert.NoError(t, testEngine.Sync2(new(MailBox))) + assert.NoError(t, testEngine.Sync(new(MailBox))) var inserts = []*MailBox{ { @@ -103,7 +103,7 @@ func TestCacheFind2(t *testing.T) { cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) - assert.NoError(t, testEngine.Sync2(new(MailBox2))) + assert.NoError(t, testEngine.Sync(new(MailBox2))) var inserts = []*MailBox2{ { @@ -154,7 +154,7 @@ func TestCacheGet(t *testing.T) { cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) - assert.NoError(t, testEngine.Sync2(new(MailBox3))) + assert.NoError(t, testEngine.Sync(new(MailBox3))) var inserts = []*MailBox3{ { diff --git a/integrations/engine_test.go b/integrations/engine_test.go index f45d6859..74e1f903 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -53,7 +53,7 @@ func TestAutoTransaction(t *testing.T) { Created time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(TestTx))) + assert.NoError(t, testEngine.Sync(new(TestTx))) engine := testEngine.(*xorm.Engine) @@ -87,7 +87,7 @@ func assertSync(t *testing.T, beans ...interface{}) { for _, bean := range beans { t.Run(testEngine.TableName(bean, true), func(t *testing.T) { assert.NoError(t, testEngine.DropTables(bean)) - assert.NoError(t, testEngine.Sync2(bean)) + assert.NoError(t, testEngine.Sync(bean)) }) } } diff --git a/integrations/performance_test.go b/integrations/performance_test.go index 241dc7b9..4646afa2 100644 --- a/integrations/performance_test.go +++ b/integrations/performance_test.go @@ -21,7 +21,7 @@ func BenchmarkGetVars(b *testing.B) { Name string } - assert.NoError(b, testEngine.Sync2(new(BenchmarkGetVars))) + assert.NoError(b, testEngine.Sync(new(BenchmarkGetVars))) var v = BenchmarkGetVars{ Name: "myname", @@ -52,7 +52,7 @@ func BenchmarkGetStruct(b *testing.B) { Name string } - assert.NoError(b, testEngine.Sync2(new(BenchmarkGetStruct))) + assert.NoError(b, testEngine.Sync(new(BenchmarkGetStruct))) var v = BenchmarkGetStruct{ Name: "myname", @@ -84,7 +84,7 @@ func BenchmarkFindStruct(b *testing.B) { Name string } - assert.NoError(b, testEngine.Sync2(new(BenchmarkFindStruct))) + assert.NoError(b, testEngine.Sync(new(BenchmarkFindStruct))) var v = BenchmarkFindStruct{ Name: "myname", diff --git a/integrations/processors_test.go b/integrations/processors_test.go index e349988d..b32f6fbb 100644 --- a/integrations/processors_test.go +++ b/integrations/processors_test.go @@ -23,7 +23,7 @@ func TestBefore_Get(t *testing.T) { Val string `xorm:"-"` } - assert.NoError(t, testEngine.Sync2(new(BeforeTable))) + assert.NoError(t, testEngine.Sync(new(BeforeTable))) cnt, err := testEngine.Insert(&BeforeTable{ Name: "test", @@ -50,7 +50,7 @@ func TestBefore_Find(t *testing.T) { Val string `xorm:"-"` } - assert.NoError(t, testEngine.Sync2(new(BeforeTable2))) + assert.NoError(t, testEngine.Sync(new(BeforeTable2))) cnt, err := testEngine.Insert([]BeforeTable2{ {Name: "test1"}, diff --git a/integrations/rows_test.go b/integrations/rows_test.go index a5648675..10f11453 100644 --- a/integrations/rows_test.go +++ b/integrations/rows_test.go @@ -18,7 +18,7 @@ func TestRows(t *testing.T) { IsMan bool } - assert.NoError(t, testEngine.Sync2(new(UserRows))) + assert.NoError(t, testEngine.Sync(new(UserRows))) cnt, err := testEngine.Insert(&UserRows{ IsMan: true, @@ -94,7 +94,7 @@ func TestRowsMyTableName(t *testing.T) { var tableName = "user_rows_my_table_name" - assert.NoError(t, testEngine.Table(tableName).Sync2(new(UserRowsMyTable))) + assert.NoError(t, testEngine.Table(tableName).Sync(new(UserRowsMyTable))) cnt, err := testEngine.Table(tableName).Insert(&UserRowsMyTable{ IsMan: true, @@ -141,7 +141,7 @@ func (UserRowsSpecTable) TableName() string { func TestRowsSpecTableName(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(UserRowsSpecTable))) + assert.NoError(t, testEngine.Sync(new(UserRowsSpecTable))) cnt, err := testEngine.Insert(&UserRowsSpecTable{ IsMan: true, diff --git a/integrations/session_cols_test.go b/integrations/session_cols_test.go index 18ba4001..462ea7c7 100644 --- a/integrations/session_cols_test.go +++ b/integrations/session_cols_test.go @@ -20,7 +20,7 @@ func TestSetExpr(t *testing.T) { Title string } - assert.NoError(t, testEngine.Sync2(new(UserExprIssue))) + assert.NoError(t, testEngine.Sync(new(UserExprIssue))) var issue = UserExprIssue{ Title: "my issue", @@ -36,7 +36,7 @@ func TestSetExpr(t *testing.T) { Show bool } - assert.NoError(t, testEngine.Sync2(new(UserExpr))) + assert.NoError(t, testEngine.Sync(new(UserExpr))) cnt, err = testEngine.Insert(&UserExpr{ Show: true, diff --git a/integrations/session_cond_test.go b/integrations/session_cond_test.go index 72a4caf5..0597d74e 100644 --- a/integrations/session_cond_test.go +++ b/integrations/session_cond_test.go @@ -104,7 +104,7 @@ func TestBuilder(t *testing.T) { func TestIn(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(Userinfo))) + assert.NoError(t, testEngine.Sync(new(Userinfo))) cnt, err := testEngine.Insert([]Userinfo{ { @@ -203,7 +203,7 @@ func TestFindAndCount(t *testing.T) { Name string } - assert.NoError(t, testEngine.Sync2(new(FindAndCount))) + assert.NoError(t, testEngine.Sync(new(FindAndCount))) _, err := testEngine.Insert([]FindAndCount{ { diff --git a/integrations/session_count_test.go b/integrations/session_count_test.go index e49b6045..13d84edb 100644 --- a/integrations/session_count_test.go +++ b/integrations/session_count_test.go @@ -17,7 +17,7 @@ func TestCount(t *testing.T) { type UserinfoCount struct { Departname string } - assert.NoError(t, testEngine.Sync2(new(UserinfoCount))) + assert.NoError(t, testEngine.Sync(new(UserinfoCount))) colName := testEngine.GetColumnMapper().Obj2Table("Departname") var cond builder.Cond = builder.Eq{ diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index 56f6f5b8..e8761acf 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -22,7 +22,7 @@ func TestDelete(t *testing.T) { IsMan bool } - assert.NoError(t, testEngine.Sync2(new(UserinfoDelete))) + assert.NoError(t, testEngine.Sync(new(UserinfoDelete))) session := testEngine.NewSession() defer session.Close() @@ -250,7 +250,7 @@ func TestDelete2(t *testing.T) { IsMan bool } - assert.NoError(t, testEngine.Sync2(new(UserinfoDelete2))) + assert.NoError(t, testEngine.Sync(new(UserinfoDelete2))) user := UserinfoDelete2{} cnt, err := testEngine.Insert(&user) diff --git a/integrations/session_exist_test.go b/integrations/session_exist_test.go index a0f65211..ca1e66ad 100644 --- a/integrations/session_exist_test.go +++ b/integrations/session_exist_test.go @@ -99,7 +99,7 @@ func TestExistStructForJoin(t *testing.T) { Name string } - assert.NoError(t, testEngine.Sync2(new(Number), new(OrderList), new(Player))) + assert.NoError(t, testEngine.Sync(new(Number), new(OrderList), new(Player))) var ply Player cnt, err := testEngine.Insert(&ply) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 9b503f25..f7af45fa 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -33,7 +33,7 @@ func TestJoinLimit(t *testing.T) { Name string } - assert.NoError(t, testEngine.Sync2(new(Salary), new(CheckList), new(Empsetting))) + assert.NoError(t, testEngine.Sync(new(Salary), new(CheckList), new(Empsetting))) var emp Empsetting cnt, err := testEngine.Insert(&emp) @@ -121,7 +121,7 @@ func (TeamUser) TableName() string { func TestFind3(t *testing.T) { var teamUser = new(TeamUser) assert.NoError(t, PrepareEngine()) - err := testEngine.Sync2(new(Team), teamUser) + err := testEngine.Sync(new(Team), teamUser) assert.NoError(t, err) var teams []Team @@ -690,7 +690,7 @@ func TestFindAndCountWithGroupBy(t *testing.T) { Name string } - assert.NoError(t, testEngine.Sync2(new(FindAndCountWithGroupBy))) + assert.NoError(t, testEngine.Sync(new(FindAndCountWithGroupBy))) _, err := testEngine.Insert([]FindAndCountWithGroupBy{ { diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 6f4c1dbe..601d4a26 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -33,7 +33,7 @@ func TestGetVar(t *testing.T) { Created time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(GetVar))) + assert.NoError(t, testEngine.Sync(new(GetVar))) var data = GetVar{ Msg: "hi", @@ -235,7 +235,7 @@ func TestGetStruct(t *testing.T) { IsMan bool } - assert.NoError(t, testEngine.Sync2(new(UserinfoGet))) + assert.NoError(t, testEngine.Sync(new(UserinfoGet))) session := testEngine.NewSession() defer session.Close() @@ -266,7 +266,7 @@ func TestGetStruct(t *testing.T) { Total int64 } - assert.NoError(t, testEngine.Sync2(&NoIdUser{})) + assert.NoError(t, testEngine.Sync(&NoIdUser{})) userCol := testEngine.GetColumnMapper().Obj2Table("User") _, err = testEngine.Where("`"+userCol+"` = ?", "xlw").Delete(&NoIdUser{}) @@ -576,7 +576,7 @@ func (MyGetCustomTableImpletation) TableName() string { func TestGetCustomTableInterface(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Table(getCustomTableName).Sync2(new(MyGetCustomTableImpletation))) + assert.NoError(t, testEngine.Table(getCustomTableName).Sync(new(MyGetCustomTableImpletation))) exist, err := testEngine.IsTableExist(getCustomTableName) assert.NoError(t, err) diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 70ec13f3..fb0ee2d1 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -25,7 +25,7 @@ func TestInsertOne(t *testing.T) { Created time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(Test))) + assert.NoError(t, testEngine.Sync(new(Test))) data := Test{Msg: "hi"} _, err := testEngine.InsertOne(data) @@ -39,7 +39,7 @@ func TestInsertMulti(t *testing.T) { Name string `xorm:"varchar(255)"` } - assert.NoError(t, testEngine.Sync2(new(TestMulti))) + assert.NoError(t, testEngine.Sync(new(TestMulti))) num, err := insertMultiDatas(1, append([]TestMulti{}, TestMulti{1, "test1"}, TestMulti{2, "test2"}, TestMulti{3, "test3"})) @@ -115,7 +115,7 @@ func TestInsertOneIfPkIsPoint(t *testing.T) { Created *time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(TestPoint))) + assert.NoError(t, testEngine.Sync(new(TestPoint))) msg := "hi" data := TestPoint{Msg: &msg} _, err := testEngine.InsertOne(&data) @@ -131,7 +131,7 @@ func TestInsertOneIfPkIsPointRename(t *testing.T) { Created *time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(TestPoint2))) + assert.NoError(t, testEngine.Sync(new(TestPoint2))) msg := "hi" data := TestPoint2{Msg: &msg} _, err := testEngine.InsertOne(&data) @@ -181,7 +181,7 @@ func TestInsertDefault(t *testing.T) { } di := new(DefaultInsert) - err := testEngine.Sync2(di) + err := testEngine.Sync(di) assert.NoError(t, err) var di2 = DefaultInsert{Name: "test"} @@ -207,7 +207,7 @@ func TestInsertDefault2(t *testing.T) { } di := new(DefaultInsert2) - err := testEngine.Sync2(di) + err := testEngine.Sync(di) assert.NoError(t, err) var di2 = DefaultInsert2{Name: "test"} @@ -258,7 +258,7 @@ func TestInsertCreated(t *testing.T) { assert.NoError(t, PrepareEngine()) di := new(CreatedInsert) - err := testEngine.Sync2(di) + err := testEngine.Sync(di) assert.NoError(t, err) ci := &CreatedInsert{} @@ -271,7 +271,7 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci.Created.Unix(), di.Created.Unix()) di2 := new(CreatedInsert2) - err = testEngine.Sync2(di2) + err = testEngine.Sync(di2) assert.NoError(t, err) ci2 := &CreatedInsert2{} @@ -284,7 +284,7 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci2.Created, di2.Created) di3 := new(CreatedInsert3) - err = testEngine.Sync2(di3) + err = testEngine.Sync(di3) assert.NoError(t, err) ci3 := &CreatedInsert3{} @@ -297,7 +297,7 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci3.Created, di3.Created) di4 := new(CreatedInsert4) - err = testEngine.Sync2(di4) + err = testEngine.Sync(di4) assert.NoError(t, err) ci4 := &CreatedInsert4{} @@ -310,7 +310,7 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci4.Created, di4.Created) di5 := new(CreatedInsert5) - err = testEngine.Sync2(di5) + err = testEngine.Sync(di5) assert.NoError(t, err) ci5 := &CreatedInsert5{} @@ -323,7 +323,7 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci5.Created.Unix(), di5.Created.Unix()) di6 := new(CreatedInsert6) - err = testEngine.Sync2(di6) + err = testEngine.Sync(di6) assert.NoError(t, err) oldTime := time.Now().Add(-time.Hour) @@ -390,7 +390,7 @@ func TestCreatedJsonTime(t *testing.T) { assert.NoError(t, PrepareEngine()) di5 := new(MyJSONTime) - err := testEngine.Sync2(di5) + err := testEngine.Sync(di5) assert.NoError(t, err) ci5 := &MyJSONTime{} @@ -489,7 +489,7 @@ func TestInsertCreatedInt64(t *testing.T) { Created int64 `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(TestCreatedInt64))) + assert.NoError(t, testEngine.Sync(new(TestCreatedInt64))) data := TestCreatedInt64{Msg: "hi"} now := time.Now() @@ -896,7 +896,7 @@ func TestMultipleInsertTableName(t *testing.T) { assert.NoError(t, PrepareEngine()) tableName := `prd_nightly_rate_16` - assert.NoError(t, testEngine.Table(tableName).Sync2(new(NightlyRate))) + assert.NoError(t, testEngine.Table(tableName).Sync(new(NightlyRate))) trans := testEngine.NewSession() defer trans.Close() @@ -932,7 +932,7 @@ func TestInsertMultiWithOmit(t *testing.T) { Omitted string `xorm:"varchar(255) 'omitted'"` } - assert.NoError(t, testEngine.Sync2(new(TestMultiOmit))) + assert.NoError(t, testEngine.Sync(new(TestMultiOmit))) l := []interface{}{ TestMultiOmit{Id: 1, Name: "1", Omitted: "1"}, @@ -977,7 +977,7 @@ func TestInsertTwice(t *testing.T) { FieldB int } - assert.NoError(t, testEngine.Sync2(new(InsertStructA), new(InsertStructB))) + assert.NoError(t, testEngine.Sync(new(InsertStructA), new(InsertStructB))) var sliceA []InsertStructA // sliceA is empty sliceB := []InsertStructB{ @@ -1008,7 +1008,7 @@ func TestInsertIntSlice(t *testing.T) { NameIDs []int `xorm:"json notnull"` } - assert.NoError(t, testEngine.Sync2(new(InsertIntSlice))) + assert.NoError(t, testEngine.Sync(new(InsertIntSlice))) var v = InsertIntSlice{ NameIDs: []int{1, 2}, @@ -1049,7 +1049,7 @@ func TestInsertDeleted(t *testing.T) { DeletedAt time.Time `xorm:"'DELETED_AT' deleted notnull"` } // notnull tag will be ignored - err := testEngine.Sync2(new(InsertDeletedStructNotRight)) + err := testEngine.Sync(new(InsertDeletedStructNotRight)) assert.NoError(t, err) type InsertDeletedStruct struct { @@ -1057,7 +1057,7 @@ func TestInsertDeleted(t *testing.T) { DeletedAt time.Time `xorm:"'DELETED_AT' deleted"` } - assert.NoError(t, testEngine.Sync2(new(InsertDeletedStruct))) + assert.NoError(t, testEngine.Sync(new(InsertDeletedStruct))) var v InsertDeletedStruct _, err = testEngine.Insert(&v) diff --git a/integrations/session_iterate_test.go b/integrations/session_iterate_test.go index 95f5a282..fa394d17 100644 --- a/integrations/session_iterate_test.go +++ b/integrations/session_iterate_test.go @@ -18,7 +18,7 @@ func TestIterate(t *testing.T) { IsMan bool } - assert.NoError(t, testEngine.Sync2(new(UserIterate))) + assert.NoError(t, testEngine.Sync(new(UserIterate))) cnt, err := testEngine.Insert(&UserIterate{ IsMan: true, @@ -46,7 +46,7 @@ func TestBufferIterate(t *testing.T) { IsMan bool } - assert.NoError(t, testEngine.Sync2(new(UserBufferIterate))) + assert.NoError(t, testEngine.Sync(new(UserBufferIterate))) var size = 20 for i := 0; i < size; i++ { diff --git a/integrations/session_pk_test.go b/integrations/session_pk_test.go index 8f7dcb55..e1aa8ed4 100644 --- a/integrations/session_pk_test.go +++ b/integrations/session_pk_test.go @@ -607,7 +607,7 @@ func TestCompositePK(t *testing.T) { assert.NoError(t, err) assertSync(t, new(TaskSolution)) - assert.NoError(t, testEngine.Sync2(new(TaskSolution))) + assert.NoError(t, testEngine.Sync(new(TaskSolution))) tables2, err := testEngine.DBMetas() assert.NoError(t, err) diff --git a/integrations/session_query_test.go b/integrations/session_query_test.go index edc77aec..b72f7ef2 100644 --- a/integrations/session_query_test.go +++ b/integrations/session_query_test.go @@ -26,7 +26,7 @@ func TestQueryString(t *testing.T) { Created time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(GetVar2))) + assert.NoError(t, testEngine.Sync(new(GetVar2))) var data = GetVar2{ Msg: "hi", @@ -54,7 +54,7 @@ func TestQueryString2(t *testing.T) { Msg bool } - assert.NoError(t, testEngine.Sync2(new(GetVar3))) + assert.NoError(t, testEngine.Sync(new(GetVar3))) var data = GetVar3{ Msg: false, @@ -91,7 +91,7 @@ func TestQueryInterface(t *testing.T) { Created time.Time `xorm:"created"` } - assert.NoError(t, testEngine.Sync2(new(GetVarInterface))) + assert.NoError(t, testEngine.Sync(new(GetVarInterface))) var data = GetVarInterface{ Msg: "hi", @@ -124,7 +124,7 @@ func TestQueryNoParams(t *testing.T) { testEngine.ShowSQL(true) - assert.NoError(t, testEngine.Sync2(new(QueryNoParams))) + assert.NoError(t, testEngine.Sync(new(QueryNoParams))) var q = QueryNoParams{ Msg: "message", @@ -168,7 +168,7 @@ func TestQueryStringNoParam(t *testing.T) { Msg bool } - assert.NoError(t, testEngine.Sync2(new(GetVar4))) + assert.NoError(t, testEngine.Sync(new(GetVar4))) var data = GetVar4{ Msg: false, @@ -205,7 +205,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { Msg bool } - assert.NoError(t, testEngine.Sync2(new(GetVar6))) + assert.NoError(t, testEngine.Sync(new(GetVar6))) var data = GetVar6{ Msg: false, @@ -242,7 +242,7 @@ func TestQueryInterfaceNoParam(t *testing.T) { Msg bool } - assert.NoError(t, testEngine.Sync2(new(GetVar5))) + assert.NoError(t, testEngine.Sync(new(GetVar5))) var data = GetVar5{ Msg: false, @@ -276,7 +276,7 @@ func TestQueryWithBuilder(t *testing.T) { testEngine.ShowSQL(true) - assert.NoError(t, testEngine.Sync2(new(QueryWithBuilder))) + assert.NoError(t, testEngine.Sync(new(QueryWithBuilder))) var q = QueryWithBuilder{ Msg: "message", @@ -325,7 +325,7 @@ func TestJoinWithSubQuery(t *testing.T) { testEngine.ShowSQL(true) - assert.NoError(t, testEngine.Sync2(new(JoinWithSubQuery1), new(JoinWithSubQueryDepart))) + assert.NoError(t, testEngine.Sync(new(JoinWithSubQuery1), new(JoinWithSubQueryDepart))) var depart = JoinWithSubQueryDepart{ Name: "depart1", @@ -375,7 +375,7 @@ func TestQueryStringWithLimit(t *testing.T) { Money float32 } - assert.NoError(t, testEngine.Sync2(new(QueryWithLimit))) + assert.NoError(t, testEngine.Sync(new(QueryWithLimit))) data, err := testEngine.Table("query_with_limit").Limit(20, 20).QueryString() assert.NoError(t, err) diff --git a/integrations/session_raw_test.go b/integrations/session_raw_test.go index 44718f46..5fa48d6e 100644 --- a/integrations/session_raw_test.go +++ b/integrations/session_raw_test.go @@ -20,7 +20,7 @@ func TestExecAndQuery(t *testing.T) { Name string } - assert.NoError(t, testEngine.Sync2(new(UserinfoQuery))) + assert.NoError(t, testEngine.Sync(new(UserinfoQuery))) res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_query`", true)+" (`uid`, `name`) VALUES (?, ?)", 1, "user") assert.NoError(t, err) @@ -46,7 +46,7 @@ func TestExecTime(t *testing.T) { Created time.Time } - assert.NoError(t, testEngine.Sync2(new(UserinfoExecTime))) + assert.NoError(t, testEngine.Sync(new(UserinfoExecTime))) now := time.Now() res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_exec_time`", true)+" (`uid`, `name`, `created`) VALUES (?, ?, ?)", 1, "user", now) assert.NoError(t, err) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 98ad9657..c4ba4c7d 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -173,7 +173,7 @@ func (s *SyncTable3) TableName() string { func TestSyncTable(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(SyncTable1))) + assert.NoError(t, testEngine.Sync(new(SyncTable1))) tables, err := testEngine.DBMetas() assert.NoError(t, err) @@ -183,7 +183,7 @@ func TestSyncTable(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name"))) - assert.NoError(t, testEngine.Sync2(new(SyncTable2))) + assert.NoError(t, testEngine.Sync(new(SyncTable2))) tables, err = testEngine.DBMetas() assert.NoError(t, err) @@ -193,7 +193,7 @@ func TestSyncTable(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name"))) - assert.NoError(t, testEngine.Sync2(new(SyncTable3))) + assert.NoError(t, testEngine.Sync(new(SyncTable3))) tables, err = testEngine.DBMetas() assert.NoError(t, err) @@ -207,7 +207,7 @@ func TestSyncTable(t *testing.T) { func TestSyncTable2(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Table("sync_tablex").Sync2(new(SyncTable1))) + assert.NoError(t, testEngine.Table("sync_tablex").Sync(new(SyncTable1))) tables, err := testEngine.DBMetas() assert.NoError(t, err) @@ -220,7 +220,7 @@ func TestSyncTable2(t *testing.T) { NewCol string } - assert.NoError(t, testEngine.Table("sync_tablex").Sync2(new(SyncTable4))) + assert.NoError(t, testEngine.Table("sync_tablex").Sync(new(SyncTable4))) tables, err = testEngine.DBMetas() assert.NoError(t, err) assert.EqualValues(t, 1, len(tables)) @@ -241,7 +241,7 @@ func TestSyncTable3(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(SyncTable5))) + assert.NoError(t, testEngine.Sync(new(SyncTable5))) tables, err := testEngine.DBMetas() assert.NoError(t, err) @@ -272,7 +272,7 @@ func TestSyncTable3(t *testing.T) { }() assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(SyncTable5))) + assert.NoError(t, testEngine.Sync(new(SyncTable5))) tables, err := testEngine.DBMetas() assert.NoError(t, err) @@ -294,9 +294,9 @@ func TestSyncTable4(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(SyncTable6))) + assert.NoError(t, testEngine.Sync(new(SyncTable6))) - assert.NoError(t, testEngine.Sync2(new(SyncTable6))) + assert.NoError(t, testEngine.Sync(new(SyncTable6))) } func TestIsTableExist(t *testing.T) { @@ -335,7 +335,7 @@ func TestIsTableEmpty(t *testing.T) { assert.NoError(t, testEngine.DropTables(&PictureEmpty{}, &NumericEmpty{})) - assert.NoError(t, testEngine.Sync2(new(PictureEmpty), new(NumericEmpty))) + assert.NoError(t, testEngine.Sync(new(PictureEmpty), new(NumericEmpty))) isEmpty, err := testEngine.IsTableEmpty(&PictureEmpty{}) assert.NoError(t, err) @@ -393,7 +393,7 @@ func TestIndexAndUnique(t *testing.T) { func TestMetaInfo(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(CustomTableName), new(IndexOrUnique))) + assert.NoError(t, testEngine.Sync(new(CustomTableName), new(IndexOrUnique))) tables, err := testEngine.DBMetas() assert.NoError(t, err) @@ -423,8 +423,8 @@ func TestSync2_1(t *testing.T) { assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.DropTables("wx_test")) - assert.NoError(t, testEngine.Sync2(new(WxTest))) - assert.NoError(t, testEngine.Sync2(new(WxTest))) + assert.NoError(t, testEngine.Sync(new(WxTest))) + assert.NoError(t, testEngine.Sync(new(WxTest))) } func TestUnique_1(t *testing.T) { @@ -440,7 +440,7 @@ func TestUnique_1(t *testing.T) { assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.DropTables("user_unique")) - assert.NoError(t, testEngine.Sync2(new(UserUnique))) + assert.NoError(t, testEngine.Sync(new(UserUnique))) assert.NoError(t, testEngine.DropTables("user_unique")) assert.NoError(t, testEngine.CreateTables(new(UserUnique))) @@ -459,7 +459,7 @@ func TestSync2_2(t *testing.T) { for i := 0; i < 10; i++ { tableName := fmt.Sprintf("test_sync2_index_%d", i) tableNames[tableName] = true - assert.NoError(t, testEngine.Table(tableName).Sync2(new(TestSync2Index))) + assert.NoError(t, testEngine.Table(tableName).Sync(new(TestSync2Index))) exist, err := testEngine.IsTableExist(tableName) assert.NoError(t, err) @@ -484,7 +484,7 @@ func TestSync2_Default(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(TestSync2Default)) - assert.NoError(t, testEngine.Sync2(new(TestSync2Default))) + assert.NoError(t, testEngine.Sync(new(TestSync2Default))) } func TestSync2_Default2(t *testing.T) { @@ -497,9 +497,9 @@ func TestSync2_Default2(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(TestSync2Default2)) - assert.NoError(t, testEngine.Sync2(new(TestSync2Default2))) - assert.NoError(t, testEngine.Sync2(new(TestSync2Default2))) - assert.NoError(t, testEngine.Sync2(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) + assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) assert.NoError(t, testEngine.Sync(new(TestSync2Default2))) diff --git a/integrations/session_sum_test.go b/integrations/session_sum_test.go index b447c699..e000233b 100644 --- a/integrations/session_sum_test.go +++ b/integrations/session_sum_test.go @@ -23,7 +23,7 @@ func TestSum(t *testing.T) { } assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(SumStruct))) + assert.NoError(t, testEngine.Sync(new(SumStruct))) var ( cases = []SumStruct{ @@ -82,7 +82,7 @@ func (s SumStructWithTableName) TableName() string { func TestSumWithTableName(t *testing.T) { assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(SumStructWithTableName))) + assert.NoError(t, testEngine.Sync(new(SumStructWithTableName))) var ( cases = []SumStructWithTableName{ @@ -146,7 +146,7 @@ func TestSumCustomColumn(t *testing.T) { } ) - assert.NoError(t, testEngine.Sync2(new(SumStruct2))) + assert.NoError(t, testEngine.Sync(new(SumStruct2))) cnt, err := testEngine.Insert(cases) assert.NoError(t, err) diff --git a/integrations/session_test.go b/integrations/session_test.go index eacf2ff5..a36b81bf 100644 --- a/integrations/session_test.go +++ b/integrations/session_test.go @@ -32,7 +32,7 @@ func TestNullFloatStruct(t *testing.T) { } assert.NoError(t, PrepareEngine()) - assert.NoError(t, testEngine.Sync2(new(MyNullFloatStruct))) + assert.NoError(t, testEngine.Sync(new(MyNullFloatStruct))) _, err := testEngine.Insert(&MyNullFloatStruct{ Uuid: "111111", diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 4312d0e0..30183382 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -27,7 +27,7 @@ func TestUpdateMap(t *testing.T) { Age int } - assert.NoError(t, testEngine.Sync2(new(UpdateTable))) + assert.NoError(t, testEngine.Sync(new(UpdateTable))) var tb = UpdateTable{ Name: "test", Age: 35, @@ -78,7 +78,7 @@ func TestUpdateLimit(t *testing.T) { Age int } - assert.NoError(t, testEngine.Sync2(new(UpdateTable2))) + assert.NoError(t, testEngine.Sync(new(UpdateTable2))) var tb = UpdateTable2{ Name: "test1", Age: 35, @@ -382,7 +382,7 @@ func TestUpdate1(t *testing.T) { assert.EqualValues(t, 1, cnt, "delete not returned 1") } - err = testEngine.StoreEngine("Innodb").Sync2(&Article{}) + err = testEngine.StoreEngine("Innodb").Sync(&Article{}) assert.NoError(t, err) defer func() { @@ -513,7 +513,7 @@ func TestUpdateUpdated(t *testing.T) { assert.NoError(t, PrepareEngine()) di := new(UpdatedUpdate) - err := testEngine.Sync2(di) + err := testEngine.Sync(di) assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate{}) @@ -529,7 +529,7 @@ func TestUpdateUpdated(t *testing.T) { assert.EqualValues(t, ci.Updated.Unix(), di.Updated.Unix()) di2 := new(UpdatedUpdate2) - err = testEngine.Sync2(di2) + err = testEngine.Sync(di2) assert.NoError(t, err) now := time.Now() @@ -556,7 +556,7 @@ func TestUpdateUpdated(t *testing.T) { assert.True(t, ci2.Updated >= di21.Updated) di3 := new(UpdatedUpdate3) - err = testEngine.Sync2(di3) + err = testEngine.Sync(di3) assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate3{}) @@ -572,7 +572,7 @@ func TestUpdateUpdated(t *testing.T) { assert.EqualValues(t, ci3.Updated, di3.Updated) di4 := new(UpdatedUpdate4) - err = testEngine.Sync2(di4) + err = testEngine.Sync(di4) assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate4{}) @@ -588,7 +588,7 @@ func TestUpdateUpdated(t *testing.T) { assert.EqualValues(t, ci4.Updated, di4.Updated) di5 := new(UpdatedUpdate5) - err = testEngine.Sync2(di5) + err = testEngine.Sync(di5) assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate5{}) diff --git a/integrations/tags_test.go b/integrations/tags_test.go index b5bf222e..c05a8d99 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -673,7 +673,7 @@ func TestCreatedUpdated(t *testing.T) { Updated time.Time `xorm:"updated"` } - err := testEngine.Sync2(&CreatedUpdated{}) + err := testEngine.Sync(&CreatedUpdated{}) assert.NoError(t, err) c := &CreatedUpdated{Name: "test"} @@ -728,7 +728,7 @@ type Lowercase struct { func TestLowerCase(t *testing.T) { assert.NoError(t, PrepareEngine()) - err := testEngine.Sync2(&Lowercase{}) + err := testEngine.Sync(&Lowercase{}) assert.NoError(t, err) _, err = testEngine.Where("`id` > 0").Delete(&Lowercase{}) assert.NoError(t, err) @@ -827,7 +827,7 @@ func TestTagComment(t *testing.T) { assert.True(t, cols[0].DefaultIsEmpty) assert.EqualValues(t, "", cols[0].Default) - assert.NoError(t, testEngine.Sync2(new(TestComment1))) + assert.NoError(t, testEngine.Sync(new(TestComment1))) tables, err := testEngine.DBMetas() assert.NoError(t, err) @@ -851,7 +851,7 @@ func TestTagComment(t *testing.T) { assert.True(t, cols[0].DefaultIsEmpty) assert.EqualValues(t, "", cols[0].Default) - assert.NoError(t, testEngine.Sync2(new(TestComment2))) + assert.NoError(t, testEngine.Sync(new(TestComment2))) tables, err = testEngine.DBMetas() assert.NoError(t, err) diff --git a/integrations/types_test.go b/integrations/types_test.go index 48facb21..d166845e 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -28,7 +28,7 @@ func TestArrayField(t *testing.T) { Name [20]byte `xorm:"char(80)"` } - assert.NoError(t, testEngine.Sync2(new(ArrayStruct))) + assert.NoError(t, testEngine.Sync(new(ArrayStruct))) var as = ArrayStruct{ Name: [20]byte{ @@ -90,7 +90,7 @@ func TestGetBytes(t *testing.T) { Data []byte `xorm:"VARBINARY(250)"` } - err := testEngine.Sync2(new(Varbinary)) + err := testEngine.Sync(new(Varbinary)) assert.NoError(t, err) cnt, err := testEngine.Insert(&Varbinary{ @@ -193,7 +193,7 @@ func TestConversion(t *testing.T) { c := new(ConvStruct) assert.NoError(t, testEngine.DropTables(c)) - assert.NoError(t, testEngine.Sync2(c)) + assert.NoError(t, testEngine.Sync(c)) var s ConvString = "sssss" c.Conv = "tttt" diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 3d7aa189..8f15a025 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -31,7 +31,7 @@ var ( { ID: "201608301400", Migrate: func(tx *xorm.Engine) error { - return tx.Sync2(&Person{}) + return tx.Sync(&Person{}) }, Rollback: func(tx *xorm.Engine) error { return tx.DropTables(&Person{}) @@ -40,7 +40,7 @@ var ( { ID: "201608301430", Migrate: func(tx *xorm.Engine) error { - return tx.Sync2(&Pet{}) + return tx.Sync(&Pet{}) }, Rollback: func(tx *xorm.Engine) error { return tx.DropTables(&Pet{}) @@ -103,10 +103,10 @@ func TestInitSchema(t *testing.T) { m := New(db, DefaultOptions, migrations) m.InitSchema(func(tx *xorm.Engine) error { - if err := tx.Sync2(&Person{}); err != nil { + if err := tx.Sync(&Person{}); err != nil { return err } - return tx.Sync2(&Pet{}) + return tx.Sync(&Pet{}) }) err = m.Migrate() diff --git a/session_schema.go b/session_schema.go index 73352135..e9ed9ec5 100644 --- a/session_schema.go +++ b/session_schema.go @@ -263,7 +263,13 @@ func (session *Session) addUnique(tableName, uqeName string) error { } // Sync2 synchronize structs to database tables +// Depricated func (session *Session) Sync2(beans ...interface{}) error { + return session.Sync(beans...) +} + +// Sync synchronize structs to database tables +func (session *Session) Sync(beans ...interface{}) error { engine := session.engine if session.isAutoClose { From e5c89cf55e83b2f0d9e97cd6adb54a03b3f19237 Mon Sep 17 00:00:00 2001 From: finelog Date: Thu, 26 Aug 2021 00:03:18 +0800 Subject: [PATCH 122/179] fix panic when convert sql and args with nil time.Time pointer (#2038) this pr fix a panic, when using nil time.Time pointer for input. not sure if there are others similar code. please review it, thanks! Co-authored-by: finelog Reviewed-on: https://gitea.com/xorm/xorm/pulls/2038 Reviewed-by: Lunny Xiao Co-authored-by: finelog Co-committed-by: finelog --- internal/statements/statement.go | 4 ++-- internal/statements/statement_test.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index b85773db..80451f50 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -962,9 +962,9 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string, if len(sqlOrArgs) > 1 { var newArgs = make([]interface{}, 0, len(sqlOrArgs)-1) for _, arg := range sqlOrArgs[1:] { - if v, ok := arg.(*time.Time); ok { + if v, ok := arg.(time.Time); ok { newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05")) - } else if v, ok := arg.(time.Time); ok { + } else if v, ok := arg.(*time.Time); ok && v != nil { newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05")) } else { newArgs = append(newArgs, arg) diff --git a/internal/statements/statement_test.go b/internal/statements/statement_test.go index ba92330e..abe0e0f2 100644 --- a/internal/statements/statement_test.go +++ b/internal/statements/statement_test.go @@ -77,6 +77,23 @@ func TestColumnsStringGeneration(t *testing.T) { } } +func TestConvertSQLOrArgs(t *testing.T) { + statement, err := createTestStatement() + assert.NoError(t, err) + + // example orm struct + // type Table struct { + // ID int + // del *time.Time `xorm:"deleted"` + // } + args := []interface{}{ + "INSERT `table` (`id`, `del`) VALUES (?, ?)", 1, (*time.Time)(nil), + } + // before fix, here will panic + _, _, err = statement.convertSQLOrArgs(args...) + assert.NoError(t, err) +} + func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { b.StopTimer() From 045abb07df05ff6a1633d60cd00daa0821631779 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 27 Aug 2021 17:10:16 +0800 Subject: [PATCH 123/179] Expose ScanString / ScanInterface and etc (#2039) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2039 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- scan.go | 288 ++++++++++++++++++++++++++++++++-------------- session.go | 17 ++- session_delete.go | 12 +- session_query.go | 154 ------------------------- session_raw.go | 74 +++++++++++- session_stats.go | 4 +- 6 files changed, 290 insertions(+), 259 deletions(-) delete mode 100644 session_query.go diff --git a/scan.go b/scan.go index 2788453e..d562abbc 100644 --- a/scan.go +++ b/scan.go @@ -129,60 +129,6 @@ func genScanResultsByBean(bean interface{}) (interface{}, bool, error) { } } -func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { - var scanResults = make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var s sql.NullString - scanResults[i] = &s - } - - if err := engine.driver.Scan(&dialects.ScanContext{ - DBLocation: engine.DatabaseTZ, - UserLocation: engine.TZLocation, - }, rows, types, scanResults...); err != nil { - return nil, err - } - - result := make(map[string]string, len(fields)) - for i, key := range fields { - s := scanResults[i].(*sql.NullString) - if s.String == "" { - result[key] = "" - continue - } - - if schemas.TIME_TYPE == engine.dialect.ColumnTypeKind(types[i].DatabaseTypeName()) { - t, err := convert.String2Time(s.String, engine.DatabaseTZ, engine.TZLocation) - if err != nil { - return nil, err - } - result[key] = t.Format("2006-01-02 15:04:05") - } else { - result[key] = s.String - } - } - return result, nil -} - -func row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) { - var scanResults = make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var s sql.NullString - scanResults[i] = &s - } - - if err := rows.Scan(scanResults...); err != nil { - return nil, err - } - - result := make(map[string][]byte, len(fields)) - for ii, key := range fields { - s := scanResults[ii].(*sql.NullString) - result[key] = []byte(s.String) - } - return result, nil -} - func (engine *Engine) scanStringInterface(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) { var scanResults = make([]interface{}, len(types)) for i := 0; i < len(types); i++ { @@ -262,41 +208,8 @@ func (engine *Engine) scanInterfaces(rows *core.Rows, fields []string, types []* return scanResultContainers, nil } -func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { - scanResults, err := engine.scanStringInterface(rows, fields, types) - if err != nil { - return nil, err - } - - var results = make([]string, 0, len(fields)) - for i := 0; i < len(fields); i++ { - results = append(results, scanResults[i].(*sql.NullString).String) - } - return results, nil -} - -func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - types, err := rows.ColumnTypes() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := row2mapBytes(rows, types, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - if rows.Err() != nil { - return nil, rows.Err() - } - - return resultsSlice, nil -} +//////////////////// +// row -> map[string]interface{} func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]interface{}, error) { var resultsMap = make(map[string]interface{}, len(fields)) @@ -321,3 +234,200 @@ func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, } return resultsMap, nil } + +func (engine *Engine) ScanInterfaceMap(rows *core.Rows) (map[string]interface{}, error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + return engine.row2mapInterface(rows, types, fields) +} + +func (engine *Engine) ScanInterfaceMaps(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := engine.row2mapInterface(rows, types, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + if rows.Err() != nil { + return nil, rows.Err() + } + + return resultsSlice, nil +} + +//////////////////// +// row -> map[string]string + +func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { + var scanResults = make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var s sql.NullString + scanResults[i] = &s + } + + if err := engine.driver.Scan(&dialects.ScanContext{ + DBLocation: engine.DatabaseTZ, + UserLocation: engine.TZLocation, + }, rows, types, scanResults...); err != nil { + return nil, err + } + + result := make(map[string]string, len(fields)) + for i, key := range fields { + s := scanResults[i].(*sql.NullString) + if s.String == "" { + result[key] = "" + continue + } + + if schemas.TIME_TYPE == engine.dialect.ColumnTypeKind(types[i].DatabaseTypeName()) { + t, err := convert.String2Time(s.String, engine.DatabaseTZ, engine.TZLocation) + if err != nil { + return nil, err + } + result[key] = t.Format("2006-01-02 15:04:05") + } else { + result[key] = s.String + } + } + return result, nil +} + +func (engine *Engine) ScanStringMap(rows *core.Rows) (map[string]string, error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + return engine.row2mapStr(rows, types, fields) +} + +func (engine *Engine) ScanStringMaps(rows *core.Rows) (resultsSlice []map[string]string, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + for rows.Next() { + result, err := engine.row2mapStr(rows, types, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + if rows.Err() != nil { + return nil, rows.Err() + } + + return resultsSlice, nil +} + +//////////////////// +// row -> map[string][]byte + +func convertMapStr2Bytes(m map[string]string) map[string][]byte { + var r = make(map[string][]byte, len(m)) + for k, v := range m { + r[k] = []byte(v) + } + return r +} + +func (engine *Engine) scanByteMaps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := engine.row2mapStr(rows, types, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, convertMapStr2Bytes(result)) + } + if rows.Err() != nil { + return nil, rows.Err() + } + + return resultsSlice, nil +} + +//////////////////// +// row -> []string + +func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) { + scanResults, err := engine.scanStringInterface(rows, fields, types) + if err != nil { + return nil, err + } + + var results = make([]string, 0, len(fields)) + for i := 0; i < len(fields); i++ { + results = append(results, scanResults[i].(*sql.NullString).String) + } + return results, nil +} + +func (engine *Engine) ScanStringSlice(rows *core.Rows) ([]string, error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + return engine.row2sliceStr(rows, types, fields) +} + +func (engine *Engine) ScanStringSlices(rows *core.Rows) (resultsSlice [][]string, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + types, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + for rows.Next() { + record, err := engine.row2sliceStr(rows, types, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, record) + } + if rows.Err() != nil { + return nil, rows.Err() + } + + return resultsSlice, nil +} diff --git a/session.go b/session.go index a96d2fc9..0e2c48ca 100644 --- a/session.go +++ b/session.go @@ -123,9 +123,9 @@ func newSession(engine *Engine) *Session { autoResetStatement: true, prepareStmt: false, - afterInsertBeans: make(map[interface{}]*[]func(interface{}), 0), - afterUpdateBeans: make(map[interface{}]*[]func(interface{}), 0), - afterDeleteBeans: make(map[interface{}]*[]func(interface{}), 0), + afterInsertBeans: make(map[interface{}]*[]func(interface{})), + afterUpdateBeans: make(map[interface{}]*[]func(interface{})), + afterDeleteBeans: make(map[interface{}]*[]func(interface{})), beforeClosures: make([]func(interface{}), 0), afterClosures: make([]func(interface{}), 0), afterProcessors: make([]executedProcessor, 0), @@ -684,13 +684,12 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b tempMap[lKey] = idx col, fieldValue, err := session.getField(dataStruct, table, colName, idx) - if err != nil { - if _, ok := err.(ErrFieldIsNotExist); ok { - continue - } else { - return nil, err - } + if _, ok := err.(ErrFieldIsNotExist); ok { + continue + } else if err != nil { + return nil, err } + if fieldValue == nil { continue } diff --git a/session_delete.go b/session_delete.go index 37b9c1cd..9c19e43e 100644 --- a/session_delete.go +++ b/session_delete.go @@ -40,7 +40,13 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri pkColumns := table.PKColumns() ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { - resultsSlice, err := session.queryBytes(newsql, args...) + rows, err := session.queryRows(newsql, args...) + if err != nil { + return err + } + defer rows.Close() + + resultsSlice, err := session.engine.ScanStringMaps(rows) if err != nil { return err } @@ -53,9 +59,9 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri if v, ok := data[col.Name]; !ok { return errors.New("no id") } else if col.SQLType.IsText() { - pk = append(pk, string(v)) + pk = append(pk, v) } else if col.SQLType.IsNumeric() { - id, err = strconv.ParseInt(string(v), 10, 64) + id, err = strconv.ParseInt(v, 10, 64) if err != nil { return err } diff --git a/session_query.go b/session_query.go deleted file mode 100644 index a4070985..00000000 --- a/session_query.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2017 The Xorm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xorm - -import ( - "xorm.io/xorm/core" -) - -// Query runs a raw sql and return records as []map[string][]byte -func (session *Session) Query(sqlOrArgs ...interface{}) ([]map[string][]byte, error) { - if session.isAutoClose { - defer session.Close() - } - - sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) - if err != nil { - return nil, err - } - - return session.queryBytes(sqlStr, args...) -} - -func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - types, err := rows.ColumnTypes() - if err != nil { - return nil, err - } - - for rows.Next() { - result, err := session.engine.row2mapStr(rows, types, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - if rows.Err() != nil { - return nil, rows.Err() - } - - return resultsSlice, nil -} - -func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]string, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - types, err := rows.ColumnTypes() - if err != nil { - return nil, err - } - - for rows.Next() { - record, err := session.engine.row2sliceStr(rows, types, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, record) - } - if rows.Err() != nil { - return nil, rows.Err() - } - - return resultsSlice, nil -} - -// QueryString runs a raw sql and return records as []map[string]string -func (session *Session) QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) { - if session.isAutoClose { - defer session.Close() - } - - sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) - if err != nil { - return nil, err - } - - rows, err := session.queryRows(sqlStr, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - return session.rows2Strings(rows) -} - -// QuerySliceString runs a raw sql and return records as [][]string -func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string, error) { - if session.isAutoClose { - defer session.Close() - } - - sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) - if err != nil { - return nil, err - } - - rows, err := session.queryRows(sqlStr, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - return session.rows2SliceString(rows) -} - -func (session *Session) rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - types, err := rows.ColumnTypes() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := session.engine.row2mapInterface(rows, types, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - if rows.Err() != nil { - return nil, rows.Err() - } - - return resultsSlice, nil -} - -// QueryInterface runs a raw sql and return records as []map[string]interface{} -func (session *Session) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) { - if session.isAutoClose { - defer session.Close() - } - - sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) - if err != nil { - return nil, err - } - - rows, err := session.queryRows(sqlStr, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - return session.rows2Interfaces(rows) -} diff --git a/session_raw.go b/session_raw.go index 2b488988..cee29fc7 100644 --- a/session_raw.go +++ b/session_raw.go @@ -71,14 +71,84 @@ func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row { return core.NewRow(session.queryRows(sqlStr, args...)) } -func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[string][]byte, error) { +// Query runs a raw sql and return records as []map[string][]byte +func (session *Session) Query(sqlOrArgs ...interface{}) ([]map[string][]byte, error) { + if session.isAutoClose { + defer session.Close() + } + + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) + if err != nil { + return nil, err + } + rows, err := session.queryRows(sqlStr, args...) if err != nil { return nil, err } defer rows.Close() - return rows2maps(rows) + return session.engine.scanByteMaps(rows) +} + +// QueryString runs a raw sql and return records as []map[string]string +func (session *Session) QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) { + if session.isAutoClose { + defer session.Close() + } + + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) + if err != nil { + return nil, err + } + + rows, err := session.queryRows(sqlStr, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + return session.engine.ScanStringMaps(rows) +} + +// QuerySliceString runs a raw sql and return records as [][]string +func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string, error) { + if session.isAutoClose { + defer session.Close() + } + + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) + if err != nil { + return nil, err + } + + rows, err := session.queryRows(sqlStr, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + return session.engine.ScanStringSlices(rows) +} + +// QueryInterface runs a raw sql and return records as []map[string]interface{} +func (session *Session) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) { + if session.isAutoClose { + defer session.Close() + } + + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) + if err != nil { + return nil, err + } + + rows, err := session.queryRows(sqlStr, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + return session.engine.ScanInterfaceMaps(rows) } func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { diff --git a/session_stats.go b/session_stats.go index 17d0a675..5d0da5e9 100644 --- a/session_stats.go +++ b/session_stats.go @@ -70,12 +70,12 @@ func (session *Session) SumInt(bean interface{}, columnName string) (res int64, // Sums call sum some columns. bean's non-empty fields are conditions. func (session *Session) Sums(bean interface{}, columnNames ...string) ([]float64, error) { - var res = make([]float64, len(columnNames), len(columnNames)) + var res = make([]float64, len(columnNames)) return res, session.sum(&res, bean, columnNames...) } // SumsInt sum specify columns and return as []int64 instead of []float64 func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int64, error) { - var res = make([]int64, len(columnNames), len(columnNames)) + var res = make([]int64, len(columnNames)) return res, session.sum(&res, bean, columnNames...) } From 78309606d4bb234507f8c9560ff82326932d6aab Mon Sep 17 00:00:00 2001 From: daisuzu Date: Tue, 7 Sep 2021 09:23:16 +0800 Subject: [PATCH 124/179] fix panic when `Iterate()` fails (#2040) not to call `rows.Err()` Reviewed-on: https://gitea.com/xorm/xorm/pulls/2040 Reviewed-by: Lunny Xiao Co-authored-by: daisuzu Co-committed-by: daisuzu --- rows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rows.go b/rows.go index 76fc1e90..b7984b99 100644 --- a/rows.go +++ b/rows.go @@ -140,5 +140,5 @@ func (rows *Rows) Close() error { return rows.rows.Close() } - return rows.Err() + return nil } From 16cf2442f6bec5543f611e93ea32e991f1501791 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 7 Sep 2021 16:03:08 +0800 Subject: [PATCH 125/179] Some performance optimization for get (#2043) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2043 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/performance_test.go | 2 +- integrations/session_delete_test.go | 4 +- internal/statements/query.go | 61 +++++++++++++++++------------ internal/statements/statement.go | 2 +- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/integrations/performance_test.go b/integrations/performance_test.go index 4646afa2..49183717 100644 --- a/integrations/performance_test.go +++ b/integrations/performance_test.go @@ -92,8 +92,8 @@ func BenchmarkFindStruct(b *testing.B) { _, err := testEngine.Insert(&v) assert.NoError(b, err) - b.StartTimer() var mynames = make([]BenchmarkFindStruct, 0, 1) + b.StartTimer() for i := 0; i < b.N; i++ { err := testEngine.Find(&mynames) b.StopTimer() diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index e8761acf..b4e40edb 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -213,7 +213,7 @@ func TestUnscopeDelete(t *testing.T) { cnt, err = testEngine.ID(1).Delete(&s) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.EqualValues(t, nowUnix, s.DeletedAt.Unix()) + assert.LessOrEqual(t, int(s.DeletedAt.Unix()-nowUnix), 1) var s1 UnscopeDeleteStruct has, err := testEngine.ID(1).Get(&s1) @@ -225,7 +225,7 @@ func TestUnscopeDelete(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, "test", s2.Name) - assert.EqualValues(t, nowUnix, s2.DeletedAt.Unix()) + assert.LessOrEqual(t, int(s2.DeletedAt.Unix()-nowUnix), 1) cnt, err = testEngine.ID(1).Unscoped().Delete(new(UnscopeDeleteStruct)) assert.NoError(t, err) diff --git a/internal/statements/query.go b/internal/statements/query.go index 76946cbd..c1ff8833 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -203,14 +203,42 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa return sqlStr, append(statement.joinArgs, condArgs...), nil } +func (statement *Statement) fromBuilder() *strings.Builder { + var builder strings.Builder + var quote = statement.quote + var dialect = statement.dialect + + builder.WriteString(" FROM ") + + if dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { + builder.WriteString(statement.TableName()) + } else { + builder.WriteString(quote(statement.TableName())) + } + + if statement.TableAlias != "" { + if dialect.URI().DBType == schemas.ORACLE { + builder.WriteString(" ") + } else { + builder.WriteString(" AS ") + } + builder.WriteString(quote(statement.TableAlias)) + } + if statement.JoinStr != "" { + builder.WriteString(" ") + builder.WriteString(statement.JoinStr) + } + return &builder +} + func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderBy bool) (string, []interface{}, error) { var ( distinct string dialect = statement.dialect - quote = statement.quote - fromStr = " FROM " + fromStr = statement.fromBuilder().String() top, mssqlCondi, whereStr string ) + if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { distinct = "DISTINCT " } @@ -220,24 +248,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB return "", nil, err } if len(condSQL) > 0 { - whereStr = " WHERE " + condSQL - } - - if dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { - fromStr += statement.TableName() - } else { - fromStr += quote(statement.TableName()) - } - - if statement.TableAlias != "" { - if dialect.URI().DBType == schemas.ORACLE { - fromStr += " " + quote(statement.TableAlias) - } else { - fromStr += " AS " + quote(statement.TableAlias) - } - } - if statement.JoinStr != "" { - fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr) + whereStr = fmt.Sprintf(" WHERE %s", condSQL) } pLimitN := statement.LimitN @@ -266,20 +277,20 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB } if statement.needTableName() { if len(statement.TableAlias) > 0 { - column = statement.TableAlias + "." + column + column = fmt.Sprintf("%s.%s", statement.TableAlias, column) } else { - column = statement.TableName() + "." + column + column = fmt.Sprintf("%s.%s", statement.TableName(), column) } } var orderStr string if needOrderBy && len(statement.OrderStr) > 0 { - orderStr = " ORDER BY " + statement.OrderStr + orderStr = fmt.Sprintf(" ORDER BY %s", statement.OrderStr) } var groupStr string if len(statement.GroupByStr) > 0 { - groupStr = " GROUP BY " + statement.GroupByStr + groupStr = fmt.Sprintf(" GROUP BY %s", statement.GroupByStr) } mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s%s%s))", column, statement.Start, column, fromStr, whereStr, orderStr, groupStr) @@ -311,7 +322,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB if pLimitN != nil { fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", *pLimitN, statement.Start) } else { - fmt.Fprintf(&buf, "LIMIT 0 OFFSET %v", statement.Start) + fmt.Fprintf(&buf, " LIMIT 0 OFFSET %v", statement.Start) } } else if pLimitN != nil { fmt.Fprint(&buf, " LIMIT ", *pLimitN) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 80451f50..c9882a47 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -308,7 +308,7 @@ func (statement *Statement) colName(col *schemas.Column, tableName string) strin if len(statement.TableAlias) > 0 { nm = statement.TableAlias } - return statement.quote(nm) + "." + statement.quote(col.Name) + return fmt.Sprintf("%s.%s", statement.quote(nm), statement.quote(col.Name)) } return statement.quote(col.Name) } From 4656a87f2f8715d6088dac3965ab5a0500dd2fee Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 13 Sep 2021 15:19:59 +0800 Subject: [PATCH 126/179] Fix bug (#2046) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2046 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- rows.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rows.go b/rows.go index b7984b99..110f41b6 100644 --- a/rows.go +++ b/rows.go @@ -83,12 +83,18 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { // Next move cursor to next record, return false if end has reached func (rows *Rows) Next() bool { - return rows.rows.Next() + if rows.rows != nil { + return rows.rows.Next() + } + return false } // Err returns the error, if any, that was encountered during iteration. Err may be called after an explicit or implicit Close. func (rows *Rows) Err() error { - return rows.rows.Err() + if rows.rows != nil { + return rows.rows.Err() + } + return nil } // Scan row record to bean properties From 67616990363485db11ef14e372ccb1c42643d698 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 16 Sep 2021 23:59:36 +0800 Subject: [PATCH 127/179] Fix bug of Rows (#2048) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2048 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- integrations/session_iterate_test.go | 17 +++++++++++--- session.go | 10 ++++----- session_get.go | 33 ++++++++++++---------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/integrations/session_iterate_test.go b/integrations/session_iterate_test.go index fa394d17..c5ecc593 100644 --- a/integrations/session_iterate_test.go +++ b/integrations/session_iterate_test.go @@ -26,16 +26,27 @@ func TestIterate(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + cnt, err = testEngine.Insert(&UserIterate{ + IsMan: false, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + cnt = 0 err = testEngine.Iterate(new(UserIterate), func(i int, bean interface{}) error { user := bean.(*UserIterate) - assert.EqualValues(t, 1, user.Id) - assert.EqualValues(t, true, user.IsMan) + if cnt == 0 { + assert.EqualValues(t, 1, user.Id) + assert.EqualValues(t, true, user.IsMan) + } else { + assert.EqualValues(t, 2, user.Id) + assert.EqualValues(t, false, user.IsMan) + } cnt++ return nil }) assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) + assert.EqualValues(t, 2, cnt) } func TestBufferIterate(t *testing.T) { diff --git a/session.go b/session.go index 0e2c48ca..f5b45a73 100644 --- a/session.go +++ b/session.go @@ -370,7 +370,7 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, return } -func (session *Session) getField(dataStruct *reflect.Value, table *schemas.Table, colName string, idx int) (*schemas.Column, *reflect.Value, error) { +func getField(dataStruct *reflect.Value, table *schemas.Table, colName string, idx int) (*schemas.Column, *reflect.Value, error) { var col = table.GetColumnIdx(colName, idx) if col == nil { return nil, nil, ErrFieldIsNotExist{colName, table.Name} @@ -440,7 +440,7 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, types []*sql return scanResults, nil } -func (session *Session) setJSON(fieldValue *reflect.Value, fieldType reflect.Type, scanResult interface{}) error { +func setJSON(fieldValue *reflect.Value, fieldType reflect.Type, scanResult interface{}) error { bs, ok := convert.AsBytes(scanResult) if !ok { return fmt.Errorf("unsupported database data type: %#v", scanResult) @@ -551,7 +551,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec fieldType := fieldValue.Type() if col.IsJSON { - return session.setJSON(fieldValue, fieldType, scanResult) + return setJSON(fieldValue, fieldType, scanResult) } switch fieldType.Kind() { @@ -570,7 +570,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } return nil case reflect.Complex64, reflect.Complex128: - return session.setJSON(fieldValue, fieldType, scanResult) + return setJSON(fieldValue, fieldType, scanResult) case reflect.Slice, reflect.Array: bs, ok := convert.AsBytes(scanResult) if ok && fieldType.Elem().Kind() == reflect.Uint8 { @@ -683,7 +683,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } tempMap[lKey] = idx - col, fieldValue, err := session.getField(dataStruct, table, colName, idx) + col, fieldValue, err := getField(dataStruct, table, colName, idx) if _, ok := err.(ErrFieldIsNotExist); ok { continue } else if err != nil { diff --git a/session_get.go b/session_get.go index a82cae92..48616a6b 100644 --- a/session_get.go +++ b/session_get.go @@ -173,7 +173,12 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, return true, err } - return true, session.scan(rows, table, beanKind, beans, types, fields) + if err := session.scan(rows, table, beanKind, beans, types, fields); err != nil { + return true, err + } + rows.Close() + + return true, session.executeProcessors() } func (session *Session) scan(rows *core.Rows, table *schemas.Table, firstBeanKind reflect.Kind, beans []interface{}, types []*sql.ColumnType, fields []string) error { @@ -184,7 +189,14 @@ func (session *Session) scan(rows *core.Rows, table *schemas.Table, firstBeanKin if !isScannableStruct(bean, len(types)) { break } - return session.getStruct(rows, types, fields, table, bean) + scanResults, err := session.row2Slice(rows, fields, types, bean) + if err != nil { + return err + } + + dataStruct := utils.ReflectValue(bean) + _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table) + return err case reflect.Slice: return session.getSlice(rows, types, fields, bean) case reflect.Map: @@ -268,23 +280,6 @@ func (session *Session) getMap(rows *core.Rows, types []*sql.ColumnType, fields } } -func (session *Session) getStruct(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, bean interface{}) error { - scanResults, err := session.row2Slice(rows, fields, types, bean) - if err != nil { - return err - } - // close it before convert data - rows.Close() - - dataStruct := utils.ReflectValue(bean) - _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table) - if err != nil { - return err - } - - return session.executeProcessors() -} - func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) { // if has no reftable, then don't use cache currently if !session.canCache() { From fd26f415ca07db048c77723c092e454212567b95 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 17 Sep 2021 18:47:30 +0800 Subject: [PATCH 128/179] Add test for mysql tls (#2049) Fix #1495 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2049 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- .drone.yml | 2 +- Makefile | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index faaeca17..210572b0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -73,7 +73,7 @@ steps: TEST_MYSQL_PASSWORD: commands: - make test-mysql - - TEST_QUOTE_POLICY=reserved make test-mysql + - TEST_QUOTE_POLICY=reserved make test-mysql-tls volumes: - name: cache diff --git a/Makefile b/Makefile index e9bd4129..ba5427d6 100644 --- a/Makefile +++ b/Makefile @@ -190,6 +190,18 @@ test-mysql\#%: go-check -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \ -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic +.PNONY: test-mysql-tls +test-mysql-tls: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)&tls=skip-verify" \ + -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m + +.PHONY: test-mysql-tls\#% +test-mysql-tls\#%: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)&tls=skip-verify" \ + -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + .PNONY: test-postgres test-postgres: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ From 0de285680bed16dacdf63a755d32d0edf0a5b794 Mon Sep 17 00:00:00 2001 From: undefined_ss Date: Thu, 23 Sep 2021 20:22:14 +0800 Subject: [PATCH 129/179] fix ctx override bug (#2053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 详情请参考工单:https://gitea.com/xorm/xorm/issues/2052 Co-authored-by: undefined_ss Reviewed-on: https://gitea.com/xorm/xorm/pulls/2053 Reviewed-by: Lunny Xiao Co-authored-by: undefined_ss Co-committed-by: undefined_ss --- session.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/session.go b/session.go index f5b45a73..f51fd41b 100644 --- a/session.go +++ b/session.go @@ -732,6 +732,12 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) { // Context sets the context on this session func (session *Session) Context(ctx context.Context) *Session { + if session.ctx != nil { + ctx = context.WithValue(ctx, log.SessionIDKey, session.ctx.Value(log.SessionIDKey)) + ctx = context.WithValue(ctx, log.SessionKey, session.ctx.Value(log.SessionKey)) + ctx = context.WithValue(ctx, log.SessionShowSQLKey, session.ctx.Value(log.SessionShowSQLKey)) + } + session.ctx = ctx return session } From bd5cd8cab725293299e201e7a8e864a689baf6b1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 24 Sep 2021 21:45:47 +0800 Subject: [PATCH 130/179] Fix bug of dameng scan (#2056) Fix #2055 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2056 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dameng.go | 2 +- integrations/session_insert_test.go | 36 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/dialects/dameng.go b/dialects/dameng.go index 5ba0cfb5..a8e7db70 100644 --- a/dialects/dameng.go +++ b/dialects/dameng.go @@ -1163,7 +1163,7 @@ func (d *damengDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.Colu case "TIMESTAMP": ns := t.(*sql.NullString) if !ns.Valid { - return nil + break } s := ns.String fields := strings.Split(s, "+") diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index fb0ee2d1..394e7e51 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -337,6 +337,42 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci6.Created.Unix(), di6.Created.Unix()) } +func TestInsertTime(t *testing.T) { + type InsertTimeStruct struct { + Id int64 + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + DeletedAt time.Time `xorm:"deleted"` + Stime time.Time + Etime time.Time + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(InsertTimeStruct)) + + its := &InsertTimeStruct{ + Stime: time.Now(), + Etime: time.Now(), + } + cnt, err := testEngine.Insert(its) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var itsGet InsertTimeStruct + has, err := testEngine.ID(1).Get(&itsGet) + assert.NoError(t, err) + assert.True(t, has) + assert.False(t, itsGet.Stime.IsZero()) + assert.False(t, itsGet.Etime.IsZero()) + + var itsFind []*InsertTimeStruct + err = testEngine.Find(&itsFind) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(itsFind)) + assert.False(t, itsFind[0].Stime.IsZero()) + assert.False(t, itsFind[0].Etime.IsZero()) +} + type JSONTime time.Time func (j JSONTime) format() string { From b350c289f82808cc387715938c14eb6ccea52277 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 29 Sep 2021 20:07:55 +0800 Subject: [PATCH 131/179] Fix missing quote on modifycolumnSQL (#2058) Fix #2054 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2058 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- dialects/dialect.go | 2 +- dialects/mssql.go | 2 +- dialects/postgres.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 460ab56a..f3aa7470 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -238,7 +238,7 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { // ModifyColumnSQL returns a SQL to modify SQL func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, false) - return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s) + return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", db.quoter.Quote(tableName), s) } // ForUpdateSQL returns for updateSQL diff --git a/dialects/mssql.go b/dialects/mssql.go index cd19afb9..706a754a 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -429,7 +429,7 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) { func (db *mssql) ModifyColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, false) - return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", tableName, s) + return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", db.quoter.Quote(tableName), s) } func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { diff --git a/dialects/postgres.go b/dialects/postgres.go index 822d3a70..1e99cd9d 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -994,10 +994,10 @@ func (db *postgres) IsTableExist(queryer core.Queryer, ctx context.Context, tabl func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { if len(db.getSchema()) == 0 || strings.Contains(tableName, ".") { return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", - tableName, col.Name, db.SQLType(col)) + db.quoter.Quote(tableName), db.quoter.Quote(col.Name), db.SQLType(col)) } return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s", - db.getSchema(), tableName, col.Name, db.SQLType(col)) + db.quoter.Quote(db.getSchema()), db.quoter.Quote(tableName), db.quoter.Quote(col.Name), db.SQLType(col)) } func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string { From 40a135948bf7bb4bb7f24dc0f2d39e2ea9ee0595 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 20 Oct 2021 08:53:30 +0800 Subject: [PATCH 132/179] New Prepare useage (#2061) Fix #2060, Three ways to use the `Prepare`. The first ```go engine.Prepare().Where().Get() ``` The second ```go sess := engine.NewSession() defer sess.Close() sess.Prepare().Where().Get() sess.Prepare().Where().Get() ``` The third ```go sess := engine.NewSession() defer sess.Close() sess.Begin() sess.Prepare().Where().Get() sess.Prepare().Where().Get() sess.Commit() ``` Or ```go sess := engine.NewSession() defer sess.Close() sess.Begin() sess.Prepare().Insert() sess.Prepare().Insert() sess.Commit() ``` Reviewed-on: https://gitea.com/xorm/xorm/pulls/2061 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- core/stmt.go | 2 +- integrations/session_get_test.go | 61 ++++++++++++++++++++++++++++++++ interface.go | 1 + session.go | 27 +++++++++++++- session_raw.go | 35 +++++++++--------- 5 files changed, 106 insertions(+), 20 deletions(-) diff --git a/core/stmt.go b/core/stmt.go index 260843d5..3247efed 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -93,7 +93,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result if err != nil { return nil, err } - res, err := s.Stmt.ExecContext(ctx, args) + res, err := s.Stmt.ExecContext(ctx, args...) hookCtx.End(ctx, res, err) if err := s.db.afterProcess(hookCtx); err != nil { return nil, err diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 601d4a26..5d1558f4 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -915,3 +915,64 @@ func TestGetVars(t *testing.T) { assert.EqualValues(t, "xlw", name) assert.EqualValues(t, 42, age) } + +func TestGetWithPrepare(t *testing.T) { + type GetVarsWithPrepare struct { + Id int64 + Name string + Age int + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetVarsWithPrepare)) + + _, err := testEngine.Insert(&GetVarsWithPrepare{ + Name: "xlw", + Age: 42, + }) + assert.NoError(t, err) + + var v1 GetVarsWithPrepare + has, err := testEngine.Prepare().ID(1).Get(&v1) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "xlw", v1.Name) + assert.EqualValues(t, 42, v1.Age) + + sess := testEngine.NewSession() + defer sess.Close() + + var v2 GetVarsWithPrepare + has, err = sess.Prepare().ID(1).Get(&v2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "xlw", v2.Name) + assert.EqualValues(t, 42, v2.Age) + + var v3 GetVarsWithPrepare + has, err = sess.Prepare().ID(1).Get(&v3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "xlw", v3.Name) + assert.EqualValues(t, 42, v3.Age) + + err = sess.Begin() + assert.NoError(t, err) + + cnt, err := sess.Prepare().Insert(&GetVarsWithPrepare{ + Name: "xlw2", + Age: 12, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + cnt, err = sess.Prepare().Insert(&GetVarsWithPrepare{ + Name: "xlw3", + Age: 13, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + err = sess.Commit() + assert.NoError(t, err) +} diff --git a/interface.go b/interface.go index 42dc9a0a..b9e88505 100644 --- a/interface.go +++ b/interface.go @@ -99,6 +99,7 @@ type EngineInterface interface { MapCacher(interface{}, caches.Cacher) error NewSession() *Session NoAutoTime() *Session + Prepare() *Session Quote(string) string SetCacher(string, caches.Cacher) SetConnMaxLifetime(time.Duration) diff --git a/session.go b/session.go index f51fd41b..2c916335 100644 --- a/session.go +++ b/session.go @@ -79,7 +79,8 @@ type Session struct { afterClosures []func(interface{}) afterProcessors []executedProcessor - stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) + stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) + txStmtCache map[uint32]*core.Stmt // for tx statement lastSQL string lastSQLArgs []interface{} @@ -130,6 +131,7 @@ func newSession(engine *Engine) *Session { afterClosures: make([]func(interface{}), 0), afterProcessors: make([]executedProcessor, 0), stmtCache: make(map[uint32]*core.Stmt), + txStmtCache: make(map[uint32]*core.Stmt), lastSQL: "", lastSQLArgs: make([]interface{}, 0), @@ -150,6 +152,12 @@ func (session *Session) Close() error { } } + for _, v := range session.txStmtCache { + if err := v.Close(); err != nil { + return err + } + } + if !session.isClosed { // When Close be called, if session is a transaction and do not call // Commit or Rollback, then call Rollback. @@ -160,6 +168,7 @@ func (session *Session) Close() error { } session.tx = nil session.stmtCache = nil + session.txStmtCache = nil session.isClosed = true } return nil @@ -200,6 +209,7 @@ func (session *Session) IsClosed() bool { func (session *Session) resetStatement() { if session.autoResetStatement { session.statement.Reset() + session.prepareStmt = false } } @@ -370,6 +380,21 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, return } +func (session *Session) doPrepareTx(sqlStr string) (stmt *core.Stmt, err error) { + crc := crc32.ChecksumIEEE([]byte(sqlStr)) + // TODO try hash(sqlStr+len(sqlStr)) + var has bool + stmt, has = session.txStmtCache[crc] + if !has { + stmt, err = session.tx.PrepareContext(session.ctx, sqlStr) + if err != nil { + return nil, err + } + session.txStmtCache[crc] = stmt + } + return +} + func getField(dataStruct *reflect.Value, table *schemas.Table, colName string, idx int) (*schemas.Column, *reflect.Value, error) { var col = table.GetColumnIdx(colName, idx) if col == nil { diff --git a/session_raw.go b/session_raw.go index cee29fc7..acb106a5 100644 --- a/session_raw.go +++ b/session_raw.go @@ -46,25 +46,22 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row return nil, err } - rows, err := stmt.QueryContext(session.ctx, args...) - if err != nil { - return nil, err - } - return rows, nil + return stmt.QueryContext(session.ctx, args...) } - rows, err := db.QueryContext(session.ctx, sqlStr, args...) + return db.QueryContext(session.ctx, sqlStr, args...) + } + + if session.prepareStmt { + stmt, err := session.doPrepareTx(sqlStr) if err != nil { return nil, err } - return rows, nil + + return stmt.QueryContext(session.ctx, args...) } - rows, err := session.tx.QueryContext(session.ctx, sqlStr, args...) - if err != nil { - return nil, err - } - return rows, nil + return session.tx.QueryContext(session.ctx, sqlStr, args...) } func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row { @@ -160,6 +157,13 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er session.lastSQLArgs = args if !session.isAutoCommit { + if session.prepareStmt { + stmt, err := session.doPrepareTx(sqlStr) + if err != nil { + return nil, err + } + return stmt.ExecContext(session.ctx, args...) + } return session.tx.ExecContext(session.ctx, sqlStr, args...) } @@ -168,12 +172,7 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er if err != nil { return nil, err } - - res, err := stmt.ExecContext(session.ctx, args...) - if err != nil { - return nil, err - } - return res, nil + return stmt.ExecContext(session.ctx, args...) } return session.DB().ExecContext(session.ctx, sqlStr, args...) From 26f9d619cc75ae89785f876c52286144eab7708a Mon Sep 17 00:00:00 2001 From: rennnosuke Date: Thu, 28 Oct 2021 21:21:38 +0800 Subject: [PATCH 133/179] Fix new-lined query execution in master DB node. (#2066) # Issue Such a following query is executed in master DB node with EngineGroup. ```go s := engineGroup.NewSession(); // create session from EngineGroup. sql := ` SELECT * FROM USER; `; type User struct { ... }; var users []User; err := s.Sql(sql).Find(&users); // executed to master DB node. ``` Reviewed-on: https://gitea.com/xorm/xorm/pulls/2066 Reviewed-by: Lunny Xiao Co-authored-by: rennnosuke Co-committed-by: rennnosuke --- session_raw.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session_raw.go b/session_raw.go index acb106a5..0d20dd60 100644 --- a/session_raw.go +++ b/session_raw.go @@ -33,7 +33,7 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row if session.isAutoCommit { var db *core.DB - if session.sessionType == groupSession && strings.EqualFold(sqlStr[:6], "select") { + if session.sessionType == groupSession && strings.EqualFold(strings.TrimSpace(sqlStr)[:6], "select") { db = session.engine.engineGroup.Slave().DB() } else { db = session.DB() From 5feff03a1744041d3e013281b4fcf642eb29d6fe Mon Sep 17 00:00:00 2001 From: RenKanai Date: Fri, 29 Oct 2021 17:31:07 +0800 Subject: [PATCH 134/179] Fix README.md: Sync2 -> Sync (#2065) Co-authored-by: rennnosuke Co-authored-by: RenKanai Reviewed-on: https://gitea.com/xorm/xorm/pulls/2065 Reviewed-by: Lunny Xiao Co-authored-by: RenKanai Co-committed-by: RenKanai --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c85938a..60a4c7ff 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Firstly, we should new an engine for a database. engine, err := xorm.NewEngine(driverName, dataSourceName) ``` -* Define a struct and Sync2 table struct to database +* Define a struct and Sync table struct to database ```Go type User struct { From a22f5dce838b9cf41de0e8778570e6f147cfbab7 Mon Sep 17 00:00:00 2001 From: stepbystep2 Date: Tue, 9 Nov 2021 17:55:37 +0800 Subject: [PATCH 135/179] =?UTF-8?q?=E5=B0=91=E4=BA=86=E4=B8=AA`.`=20(#2068?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://gitea.com/xorm/xorm/pulls/2068 Reviewed-by: Lunny Xiao Co-authored-by: stepbystep2 Co-committed-by: stepbystep2 --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index f2445019..78ff34ee 100644 --- a/README_CN.md +++ b/README_CN.md @@ -214,7 +214,7 @@ type UserDetail struct { } var users []UserDetail -err := engine.Table("user").Select("user.*, detail.*") +err := engine.Table("user").Select("user.*, detail.*"). Join("INNER", "detail", "detail.user_id = user.id"). Where("user.name = ?", name).Limit(10, 0). Find(&users) From aea91cc7dede96f071514cea007bd792613ae748 Mon Sep 17 00:00:00 2001 From: fanybook Date: Fri, 12 Nov 2021 20:58:05 +0800 Subject: [PATCH 136/179] =?UTF-8?q?add=20table=20&=20column=20comment=20fo?= =?UTF-8?q?r=20postgres=EF=BC=88add=20table=20comment=20for=20mysql?= =?UTF-8?q?=EF=BC=89=20(#2067)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 让 postgres 支持字段注释,只在 v1.2.5 上测试过(不知道怎么 import master) 发现 master 分支好像大改了?模式表名带 schema 了 使用方式和 mysql 相同 ```go type User struct { Id int64 `xorm:"pk autoincr"` Name string `json:"name" xorm:"not null default '' varchar(50) index(name_age) comment('用户 (it''s) 1; 名')"` Salt string Age int `json:"age" xorm:"not null default 0 int(10) index(name_age) comment('年龄')"` Passwd string `xorm:"varchar(200)"` CreatedAt time.Time `xorm:"created"` UpdatedAt time.Time `xorm:"updated"` } _ = engine.Sync(new(User)) func (model User) TableComment() string { return "表注释" } ``` Co-authored-by: fanybook Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2067 Co-authored-by: fanybook Co-committed-by: fanybook --- dialects/dialect.go | 2 +- dialects/mysql.go | 6 +++++ dialects/postgres.go | 54 ++++++++++++++++++++++++++++++++++++++++---- names/table_name.go | 47 ++++++++++++++++++++++++++++++++++++-- session_schema.go | 2 ++ tags/parser.go | 1 + tags/parser_test.go | 44 ++++++++++++++++++++++++++++++++++++ 7 files changed, 148 insertions(+), 8 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index f3aa7470..2d772411 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -206,7 +206,7 @@ func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableNa // AddColumnSQL returns a SQL to add a column func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, true) - return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), s) + return fmt.Sprintf("ALTER TABLE %s ADD %s", db.dialect.Quoter().Quote(tableName), s) } // CreateIndexSQL returns a SQL to create index diff --git a/dialects/mysql.go b/dialects/mysql.go index ce3bd705..9cc695ef 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -685,6 +685,12 @@ func (db *mysql) CreateTableSQL(ctx context.Context, queryer core.Queryer, table b.WriteString(db.rowFormat) } + if table.Comment != "" { + b.WriteString(" COMMENT='") + b.WriteString(table.Comment) + b.WriteString("'") + } + return b.String(), true, nil } diff --git a/dialects/postgres.go b/dialects/postgres.go index 1e99cd9d..3595f6c5 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -991,13 +991,37 @@ func (db *postgres) IsTableExist(queryer core.Queryer, ctx context.Context, tabl db.getSchema(), tableName) } -func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { +func (db *postgres) AddColumnSQL(tableName string, col *schemas.Column) string { + s, _ := ColumnString(db.dialect, col, true) + + quoter := db.dialect.Quoter() + addColumnSQL := "" + commentSQL := "; " if len(db.getSchema()) == 0 || strings.Contains(tableName, ".") { - return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", - db.quoter.Quote(tableName), db.quoter.Quote(col.Name), db.SQLType(col)) + addColumnSQL = fmt.Sprintf("ALTER TABLE %s ADD %s", quoter.Quote(tableName), s) + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + return addColumnSQL + commentSQL } - return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s", - db.quoter.Quote(db.getSchema()), db.quoter.Quote(tableName), db.quoter.Quote(col.Name), db.SQLType(col)) + + addColumnSQL = fmt.Sprintf("ALTER TABLE %s.%s ADD %s", quoter.Quote(db.getSchema()), quoter.Quote(tableName), s) + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s.%s IS '%s'", quoter.Quote(db.getSchema()), quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + return addColumnSQL + commentSQL +} + +func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { + quoter := db.dialect.Quoter() + modifyColumnSQL := "" + commentSQL := "; " + + if len(db.getSchema()) == 0 || strings.Contains(tableName, ".") { + modifyColumnSQL = fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s TYPE %s", quoter.Quote(tableName), quoter.Quote(col.Name), db.SQLType(col)) + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + return modifyColumnSQL + commentSQL + } + + modifyColumnSQL = fmt.Sprintf("ALTER TABLE %s.%s ALTER COLUMN %s TYPE %s", quoter.Quote(db.getSchema()), quoter.Quote(tableName), quoter.Quote(col.Name), db.SQLType(col)) + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s.%s IS '%s'", quoter.Quote(db.getSchema()), quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + return modifyColumnSQL + commentSQL } func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string { @@ -1302,6 +1326,26 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN return indexes, nil } +func (db *postgres) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { + quoter := db.dialect.Quoter() + if len(db.getSchema()) != 0 && !strings.Contains(tableName, ".") { + tableName = fmt.Sprintf("%s.%s", db.getSchema(), tableName) + } + + createTableSQL, ok, err := db.Base.CreateTableSQL(ctx, queryer, table, tableName) + if err != nil { + return "", ok, err + } + + commentSql := "; " + if table.Comment != "" { + // support schema.table -> "schema"."table" + commentSql += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'", quoter.Quote(tableName), table.Comment) + } + + return createTableSQL + commentSql, true, nil +} + func (db *postgres) Filters() []Filter { return []Filter{&SeqFilter{Prefix: "$", Start: 1}} } diff --git a/names/table_name.go b/names/table_name.go index cc0e9274..d7d71b51 100644 --- a/names/table_name.go +++ b/names/table_name.go @@ -14,9 +14,15 @@ type TableName interface { TableName() string } +type TableComment interface { + TableComment() string +} + var ( - tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() - tvCache sync.Map + tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() + tpTableComment = reflect.TypeOf((*TableComment)(nil)).Elem() + tvCache sync.Map + tcCache sync.Map ) // GetTableName returns table name @@ -55,3 +61,40 @@ func GetTableName(mapper Mapper, v reflect.Value) string { return mapper.Obj2Table(v.Type().Name()) } + +// GetTableComment returns table comment +func GetTableComment(v reflect.Value) string { + if v.Type().Implements(tpTableComment) { + return v.Interface().(TableComment).TableComment() + } + + if v.Kind() == reflect.Ptr { + v = v.Elem() + if v.Type().Implements(tpTableComment) { + return v.Interface().(TableComment).TableComment() + } + } else if v.CanAddr() { + v1 := v.Addr() + if v1.Type().Implements(tpTableComment) { + return v1.Interface().(TableComment).TableComment() + } + } else { + comment, ok := tcCache.Load(v.Type()) + if ok { + if comment.(string) != "" { + return comment.(string) + } + } else { + v2 := reflect.New(v.Type()) + if v2.Type().Implements(tpTableComment) { + tableComment := v2.Interface().(TableComment).TableComment() + tcCache.Store(v.Type(), tableComment) + return tableComment + } + + tcCache.Store(v.Type(), "") + } + } + + return "" +} diff --git a/session_schema.go b/session_schema.go index e9ed9ec5..75140426 100644 --- a/session_schema.go +++ b/session_schema.go @@ -394,6 +394,8 @@ func (session *Session) Sync(beans ...interface{}) error { _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } } + } else if col.Comment != oriCol.Comment { + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } if col.Default != oriCol.Default { diff --git a/tags/parser.go b/tags/parser.go index efee11e7..83026862 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -316,6 +316,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table := schemas.NewEmptyTable() table.Type = t table.Name = names.GetTableName(parser.tableMapper, v) + table.Comment = names.GetTableComment(v) for i := 0; i < t.NumField(); i++ { col, err := parser.parseField(table, i, t.Field(i), v.Field(i)) diff --git a/tags/parser_test.go b/tags/parser_test.go index 70c57692..83c81a1e 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -26,6 +26,20 @@ func (p ParseTableName2) TableName() string { return "p_parseTableName" } +type ParseTableComment struct{} + +type ParseTableComment1 struct{} + +type ParseTableComment2 struct{} + +func (p ParseTableComment1) TableComment() string { + return "p_parseTableComment1" +} + +func (p *ParseTableComment2) TableComment() string { + return "p_parseTableComment2" +} + func TestParseTableName(t *testing.T) { parser := NewParser( "xorm", @@ -47,6 +61,36 @@ func TestParseTableName(t *testing.T) { assert.EqualValues(t, "p_parseTableName", table.Name) } +func TestParseTableComment(t *testing.T) { + parser := NewParser( + "xorm", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.SnakeMapper{}, + caches.NewManager(), + ) + + table, err := parser.Parse(reflect.ValueOf(new(ParseTableComment))) + assert.NoError(t, err) + assert.EqualValues(t, "", table.Comment) + + table, err = parser.Parse(reflect.ValueOf(new(ParseTableComment1))) + assert.NoError(t, err) + assert.EqualValues(t, "p_parseTableComment1", table.Comment) + + table, err = parser.Parse(reflect.ValueOf(ParseTableComment1{})) + assert.NoError(t, err) + assert.EqualValues(t, "p_parseTableComment1", table.Comment) + + table, err = parser.Parse(reflect.ValueOf(new(ParseTableComment2))) + assert.NoError(t, err) + assert.EqualValues(t, "p_parseTableComment2", table.Comment) + + table, err = parser.Parse(reflect.ValueOf(ParseTableComment2{})) + assert.NoError(t, err) + assert.EqualValues(t, "p_parseTableComment2", table.Comment) +} + func TestUnexportField(t *testing.T) { parser := NewParser( "xorm", From f35ff7c2ebd4f0442f4494dcd519f0333d5c744a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 19 Nov 2021 12:32:55 +0800 Subject: [PATCH 137/179] Make SQLType2Type support uint (#2071) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2071 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- schemas/type.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schemas/type.go b/schemas/type.go index d192bac6..8702862a 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -332,6 +332,10 @@ func SQLType2Type(st SQLType) reflect.Type { return IntType case BigInt, BigSerial: return Int64Type + case UnsignedBit, UnsignedTinyInt, UnsignedSmallInt, UnsignedMediumInt, UnsignedInt: + return UintType + case UnsignedBigInt: + return Uint64Type case Float, Real: return Float32Type case Double: From 885f582677dd3f3bbd127924b05763e730573300 Mon Sep 17 00:00:00 2001 From: RenKanai Date: Wed, 24 Nov 2021 10:29:39 +0800 Subject: [PATCH 138/179] Fix to add session.statement.IsForUpdate check in Session.queryRows() (#2064) # Issue The 'for-update' query is executed to slave DB node. # Example ```go s := engineGroup.NewSession(); // create session from EngineGroup. ... s.ForUpdate(); type User struct { ... }; var user User; has, err := s.Get(&user); // executed to slave DB node. ... ``` Co-authored-by: rennnosuke Co-authored-by: RenKanai Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2064 Reviewed-by: Lunny Xiao Co-authored-by: RenKanai Co-committed-by: RenKanai --- session_raw.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session_raw.go b/session_raw.go index 0d20dd60..add584d0 100644 --- a/session_raw.go +++ b/session_raw.go @@ -33,7 +33,7 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row if session.isAutoCommit { var db *core.DB - if session.sessionType == groupSession && strings.EqualFold(strings.TrimSpace(sqlStr)[:6], "select") { + if session.sessionType == groupSession && strings.EqualFold(strings.TrimSpace(sqlStr)[:6], "select") && !session.statement.IsForUpdate { db = session.engine.engineGroup.Slave().DB() } else { db = session.DB() From 4f8f829913c02a087d80ee30519e6c7c22e5e29c Mon Sep 17 00:00:00 2001 From: linbaozhong Date: Wed, 1 Dec 2021 13:54:47 +0800 Subject: [PATCH 139/179] =?UTF-8?q?exist=E6=96=B9=E6=B3=95sql=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E4=BC=98=E5=8C=96=20(#2075)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本地没有mssql和oracle,所以没法测试,但是,mysql 使用select 1 from TABLENAME where CONDITION limit 1可能更好 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2075 Reviewed-by: Lunny Xiao Co-authored-by: linbaozhong Co-committed-by: linbaozhong --- internal/statements/query.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/statements/query.go b/internal/statements/query.go index c1ff8833..dd38a9a2 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -392,7 +392,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac } else if statement.dialect.URI().DBType == schemas.ORACLE { sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) %s AND ROWNUM=1", tableName, joinStr, condSQL) } else { - sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL) + sqlStr = fmt.Sprintf("SELECT 1 FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL) } args = condArgs } else { @@ -401,7 +401,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac } else if statement.dialect.URI().DBType == schemas.ORACLE { sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE ROWNUM=1", tableName, joinStr) } else { - sqlStr = fmt.Sprintf("SELECT * FROM %s %s LIMIT 1", tableName, joinStr) + sqlStr = fmt.Sprintf("SELECT 1 FROM %s %s LIMIT 1", tableName, joinStr) } args = []interface{}{} } From 303b1aeb7772ebd6253c72ba4853ce8cd3dadb44 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 6 Dec 2021 13:13:47 +0800 Subject: [PATCH 140/179] Upgrade dependencies modules (#2078) - github.com/goccy/go-json v0.7.4 -> v0.8.1 - github.com/json-iterator/go v1.1.11 -> v1.1.12 - github.com/mattn/go-sqlite3 v1.14.8 -> v1.14.9 - modernc.org/sqlite v1.11.2 -> v1.14.2 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2078 Co-authored-by: Lunny Xiao Co-committed-by: Lunny Xiao --- go.mod | 9 ++-- go.sum | 133 +++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 115 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 98b5617c..0764d73a 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,16 @@ require ( gitee.com/travelliu/dm v1.8.11192 github.com/denisenkom/go-mssqldb v0.10.0 github.com/go-sql-driver/mysql v1.6.0 - github.com/goccy/go-json v0.7.4 + github.com/goccy/go-json v0.8.1 + github.com/golang/snappy v0.0.4 // indirect github.com/jackc/pgx/v4 v4.12.0 - github.com/json-iterator/go v1.1.11 + github.com/json-iterator/go v1.1.12 github.com/lib/pq v1.10.2 - github.com/mattn/go-sqlite3 v1.14.8 + github.com/mattn/go-sqlite3 v1.14.9 github.com/shopspring/decimal v1.2.0 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - modernc.org/sqlite v1.11.2 + modernc.org/sqlite v1.14.2 xorm.io/builder v0.3.9 ) diff --git a/go.sum b/go.sum index 4596f326..8e7ac44b 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= -github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= +github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -98,8 +98,9 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -110,6 +111,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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= @@ -206,8 +209,8 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -245,9 +248,8 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= -github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -261,8 +263,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -460,9 +463,10 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -542,34 +546,117 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= +modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U= +modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= +modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= +modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= +modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= +modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= +modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= +modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= +modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= +modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= +modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= +modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= +modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= +modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= +modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= +modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= +modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= +modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= +modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= +modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= +modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= +modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= +modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= +modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= +modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= +modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= +modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= +modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= +modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= +modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= +modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= +modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= +modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= +modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= +modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8= +modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= +modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA= +modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= +modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= +modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= +modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= +modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= +modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= +modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= +modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= +modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= +modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= +modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= +modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= +modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= +modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= +modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= +modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= +modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= +modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= +modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= +modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= +modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= +modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= +modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= +modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= +modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= +modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= +modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= +modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= +modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= +modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= +modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE= +modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= +modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w= -modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A= +modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE= +modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.5.5 h1:N03RwthgTR/l/eQvz3UjfYnvVVj1G2sZqzFGfoD4HE4= -modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI= +modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw= +modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= -modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= +modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao= +modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= From a2d3669edf9201a1dad307d8505de315211ab07a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 14 Dec 2021 09:00:35 +0800 Subject: [PATCH 141/179] Add README and fix some lints (#2079) as title. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2079 --- .golangci.yml | 24 ++++++++++++++++ Makefile | 22 +++++++++++++-- README.md | 18 ++++++++++++ README_CN.md | 18 ++++++++++++ caches/encode.go | 6 ++-- caches/lru.go | 24 +++++++--------- contexts/hook.go | 2 +- core/db.go | 2 +- core/db_test.go | 14 +++++----- core/rows.go | 2 +- dialects/dameng.go | 40 ++++++++++++++++++++------- dialects/dialect.go | 6 +++- dialects/mysql.go | 24 +++++----------- dialects/postgres.go | 12 ++++---- dialects/table_name.go | 21 ++++++-------- engine.go | 10 ++++--- integrations/engine_test.go | 6 ++-- integrations/processors_test.go | 4 +-- integrations/session_insert_test.go | 2 +- integrations/session_pk_test.go | 16 +++++------ integrations/session_schema_test.go | 2 +- integrations/session_update_test.go | 14 ++++++---- integrations/tags_test.go | 6 ++-- integrations/tests.go | 6 ++-- internal/statements/query.go | 12 ++++++-- internal/statements/statement.go | 18 ++---------- internal/statements/statement_test.go | 2 ++ internal/utils/slice.go | 1 + log/logger.go | 16 +++++------ log/syslogger.go | 17 ++++++------ migrate/migrate.go | 2 +- migrate/migrate_test.go | 2 +- rows.go | 2 +- scan.go | 6 ++++ schemas/index.go | 2 +- schemas/quote.go | 10 ++++--- schemas/quote_test.go | 11 ++++---- session.go | 2 +- session_delete.go | 2 +- session_find.go | 7 +++-- session_get.go | 11 +------- session_insert.go | 29 ++++++++++--------- session_iterate.go | 2 +- session_schema.go | 18 ------------ session_tx.go | 2 +- session_update.go | 11 ++++---- 46 files changed, 278 insertions(+), 208 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..7b91f22d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,24 @@ +linters: + enable: + - gosimple + - deadcode + - typecheck + - govet + - errcheck + - staticcheck + - unused + - structcheck + - varcheck + - dupl + #- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. + - gofmt + - misspell + - gocritic + - bidichk + - ineffassign + enable-all: false + disable-all: true + fast: false + +run: + timeout: 3m \ No newline at end of file diff --git a/Makefile b/Makefile index ba5427d6..220c8592 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,7 @@ help: @echo " - build creates the entire project" @echo " - clean delete integration files and build files but not css and js files" @echo " - fmt format the code" - @echo " - lint run code linter revive" + @echo " - lint run code linter" @echo " - misspell check if a word is written wrong" @echo " - test run default unit test" @echo " - test-cockroach run integration tests for cockroach" @@ -111,7 +111,25 @@ help: @echo " - vet examines Go source code and reports suspicious constructs" .PHONY: lint -lint: revive +lint: golangci-lint + +.PHONY: golangci-lint +golangci-lint: golangci-lint-check + golangci-lint run --timeout 10m + +.PHONY: golangci-lint-check +golangci-lint-check: + $(eval GOLANGCI_LINT_VERSION := $(shell printf "%03d%03d%03d" $(shell golangci-lint --version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');)) + $(eval MIN_GOLANGCI_LINT_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_GOLANGCI_LINT_VERSION) | grep -o ...))) + @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + echo "Downloading golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \ + export BINARY="golangci-lint"; \ + curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \ + elif [ "$(GOLANGCI_LINT_VERSION)" -lt "$(MIN_GOLANGCI_LINT_VERSION)" ]; then \ + echo "Downloading newer version of golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \ + export BINARY="golangci-lint"; \ + curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \ + fi .PHONY: revive revive: diff --git a/README.md b/README.md index 60a4c7ff..ccf49348 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,24 @@ affected, err := engine.Insert(&users) affected, err := engine.Insert(&user1, &users) // INSERT INTO struct1 () values () // INSERT INTO struct2 () values (),(),() + +affected, err := engine.Table("user").Insert(map[string]interface{}{ + "name": "lunny", + "age": 18, +}) +// INSERT INTO user (name, age) values (?,?) + +affected, err := engine.Table("user").Insert([]map[string]interface{}{ + { + "name": "lunny", + "age": 18, + }, + { + "name": "lunny2", + "age": 19, + }, +}) +// INSERT INTO user (name, age) values (?,?),(?,?) ``` * `Get` query one record from database diff --git a/README_CN.md b/README_CN.md index 78ff34ee..a5aaae66 100644 --- a/README_CN.md +++ b/README_CN.md @@ -138,6 +138,24 @@ affected, err := engine.Insert(&users) affected, err := engine.Insert(&user1, &users) // INSERT INTO struct1 () values () // INSERT INTO struct2 () values (),(),() + +affected, err := engine.Table("user").Insert(map[string]interface{}{ + "name": "lunny", + "age": 18, +}) +// INSERT INTO user (name, age) values (?,?) + +affected, err := engine.Table("user").Insert([]map[string]interface{}{ + { + "name": "lunny", + "age": 18, + }, + { + "name": "lunny2", + "age": 19, + }, +}) +// INSERT INTO user (name, age) values (?,?),(?,?) ``` * `Get` 查询单条记录 diff --git a/caches/encode.go b/caches/encode.go index 95536d7e..8659668c 100644 --- a/caches/encode.go +++ b/caches/encode.go @@ -16,19 +16,19 @@ import ( // Md5 return md5 hash string func Md5(str string) string { m := md5.New() - io.WriteString(m, str) + _, _ = io.WriteString(m, str) return fmt.Sprintf("%x", m.Sum(nil)) } // Encode Encode data func Encode(data interface{}) ([]byte, error) { - //return JsonEncode(data) + // return JsonEncode(data) return GobEncode(data) } // Decode decode data func Decode(data []byte, to interface{}) error { - //return JsonDecode(data, to) + // return JsonDecode(data, to) return GobDecode(data, to) } diff --git a/caches/lru.go b/caches/lru.go index 6b45ac94..885f02d6 100644 --- a/caches/lru.go +++ b/caches/lru.go @@ -56,7 +56,7 @@ func (m *LRUCacher) GC() { var removedNum int for e := m.idList.Front(); e != nil; { if removedNum <= CacheGcMaxRemoved && - time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { + time.Since(e.Value.(*idNode).lastVisit) > m.Expired { removedNum++ next := e.Next() node := e.Value.(*idNode) @@ -70,7 +70,7 @@ func (m *LRUCacher) GC() { removedNum = 0 for e := m.sqlList.Front(); e != nil; { if removedNum <= CacheGcMaxRemoved && - time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { + time.Since(e.Value.(*sqlNode).lastVisit) > m.Expired { removedNum++ next := e.Next() node := e.Value.(*sqlNode) @@ -96,7 +96,7 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} { } else { lastTime := el.Value.(*sqlNode).lastVisit // if expired, remove the node and return nil - if time.Now().Sub(lastTime) > m.Expired { + if time.Since(lastTime) > m.Expired { m.delIds(tableName, sql) return nil } @@ -122,7 +122,7 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} { if el, ok := m.idIndex[tableName][id]; ok { lastTime := el.Value.(*idNode).lastVisit // if expired, remove the node and return nil - if time.Now().Sub(lastTime) > m.Expired { + if time.Since(lastTime) > m.Expired { m.delBean(tableName, id) return nil } @@ -145,7 +145,7 @@ func (m *LRUCacher) clearIds(tableName string) { if tis, ok := m.sqlIndex[tableName]; ok { for sql, v := range tis { m.sqlList.Remove(v) - m.store.Del(sql) + _ = m.store.Del(sql) } } m.sqlIndex[tableName] = make(map[string]*list.Element) @@ -163,7 +163,7 @@ func (m *LRUCacher) clearBeans(tableName string) { for id, v := range tis { m.idList.Remove(v) tid := genID(tableName, id) - m.store.Del(tid) + _ = m.store.Del(tid) } } m.idIndex[tableName] = make(map[string]*list.Element) @@ -188,7 +188,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { } else { el.Value.(*sqlNode).lastVisit = time.Now() } - m.store.Put(sql, ids) + _ = m.store.Put(sql, ids) if m.sqlList.Len() > m.MaxElementSize { e := m.sqlList.Front() node := e.Value.(*sqlNode) @@ -210,7 +210,7 @@ func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) { el.Value.(*idNode).lastVisit = time.Now() } - m.store.Put(genID(tableName, id), obj) + _ = m.store.Put(genID(tableName, id), obj) if m.idList.Len() > m.MaxElementSize { e := m.idList.Front() node := e.Value.(*idNode) @@ -226,7 +226,7 @@ func (m *LRUCacher) delIds(tableName, sql string) { m.sqlList.Remove(el) } } - m.store.Del(sql) + _ = m.store.Del(sql) } // DelIds deletes ids @@ -243,7 +243,7 @@ func (m *LRUCacher) delBean(tableName string, id string) { m.idList.Remove(el) m.clearIds(tableName) } - m.store.Del(tid) + _ = m.store.Del(tid) } // DelBean deletes beans in some table @@ -265,10 +265,6 @@ type sqlNode struct { lastVisit time.Time } -func genSQLKey(sql string, args interface{}) string { - return fmt.Sprintf("%s-%v", sql, args) -} - func genID(prefix string, id string) string { return fmt.Sprintf("%s-%s", prefix, id) } diff --git a/contexts/hook.go b/contexts/hook.go index 70f114dd..f6d86cfc 100644 --- a/contexts/hook.go +++ b/contexts/hook.go @@ -36,7 +36,7 @@ func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) { c.Ctx = ctx c.Result = result c.Err = err - c.ExecuteTime = time.Now().Sub(c.start) + c.ExecuteTime = time.Since(c.start) } // Hook represents a hook behaviour diff --git a/core/db.go b/core/db.go index ef5ab227..b476ef9a 100644 --- a/core/db.go +++ b/core/db.go @@ -136,7 +136,7 @@ func (db *DB) reflectNew(typ reflect.Type) reflect.Value { cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0} db.reflectCache[typ] = cs } else { - cs.idx = cs.idx + 1 + cs.idx++ } return cs.value.Index(cs.idx).Addr() } diff --git a/core/db_test.go b/core/db_test.go index e9c2d82d..a9c19392 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -96,7 +96,7 @@ func BenchmarkOriQuery(b *testing.B) { if err != nil { b.Error(err) } - //fmt.Println(Id, Name, Title, Age, Alias, NickName) + // fmt.Println(Id, Name, Title, Age, Alias, NickName) } rows.Close() } @@ -245,13 +245,13 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { b.Error(err) } b.Log(slice) - switch slice[1].(type) { + switch st := slice[1].(type) { case *string: - if *slice[1].(*string) != "xlw" { + if *st != "xlw" { b.Error(errors.New("name should be xlw")) } case []byte: - if string(slice[1].([]byte)) != "xlw" { + if string(st) != "xlw" { b.Error(errors.New("name should be xlw")) } } @@ -399,14 +399,14 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { if err != nil { b.Error(err) } - switch m["name"].(type) { + switch t := m["name"].(type) { case string: - if m["name"].(string) != "xlw" { + if t != "xlw" { b.Log(m) b.Error(errors.New("name should be xlw")) } case []byte: - if string(m["name"].([]byte)) != "xlw" { + if string(t) != "xlw" { b.Log(m) b.Error(errors.New("name should be xlw")) } diff --git a/core/rows.go b/core/rows.go index c15a59a3..75d6ebf0 100644 --- a/core/rows.go +++ b/core/rows.go @@ -62,7 +62,7 @@ func (rs *Rows) ScanStructByIndex(dest ...interface{}) error { for _, vvv := range vvvs { for j := 0; j < vvv.NumField(); j++ { newDest[i] = vvv.Field(j).Addr().Interface() - i = i + 1 + i++ } } diff --git a/dialects/dameng.go b/dialects/dameng.go index a8e7db70..f4a075d5 100644 --- a/dialects/dameng.go +++ b/dialects/dameng.go @@ -670,9 +670,15 @@ func (db *dameng) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl quoter := db.Quoter() var b strings.Builder - b.WriteString("CREATE TABLE ") - quoter.QuoteTo(&b, tableName) - b.WriteString(" (") + if _, err := b.WriteString("CREATE TABLE "); err != nil { + return "", false, err + } + if err := quoter.QuoteTo(&b, tableName); err != nil { + return "", false, err + } + if _, err := b.WriteString(" ("); err != nil { + return "", false, err + } pkList := table.PrimaryKeys @@ -687,21 +693,35 @@ func (db *dameng) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl } s, _ := ColumnString(db, col, false) - b.WriteString(s) + if _, err := b.WriteString(s); err != nil { + return "", false, err + } if i != len(table.ColumnsSeq())-1 { - b.WriteString(", ") + if _, err := b.WriteString(", "); err != nil { + return "", false, err + } } } if len(pkList) > 0 { if len(table.ColumnsSeq()) > 0 { - b.WriteString(", ") + if _, err := b.WriteString(", "); err != nil { + return "", false, err + } + } + if _, err := b.WriteString(fmt.Sprintf("CONSTRAINT PK_%s PRIMARY KEY (", tableName)); err != nil { + return "", false, err + } + if err := quoter.JoinWrite(&b, pkList, ","); err != nil { + return "", false, err + } + if _, err := b.WriteString(")"); err != nil { + return "", false, err } - b.WriteString(fmt.Sprintf("CONSTRAINT PK_%s PRIMARY KEY (", tableName)) - quoter.JoinWrite(&b, pkList, ",") - b.WriteString(")") } - b.WriteString(")") + if _, err := b.WriteString(")"); err != nil { + return "", false, err + } return b.String(), false, nil } diff --git a/dialects/dialect.go b/dialects/dialect.go index 2d772411..555d96c6 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -38,11 +38,13 @@ func (uri *URI) SetSchema(schema string) { } } +// enumerates all autoincr mode const ( IncrAutoincrMode = iota SequenceAutoincrMode ) +// DialectFeatures represents a dialect parameters type DialectFeatures struct { AutoincrMode int // 0 autoincrement column, 1 sequence } @@ -126,7 +128,9 @@ func (db *Base) CreateTableSQL(ctx context.Context, queryer core.Queryer, table quoter := db.dialect.Quoter() var b strings.Builder b.WriteString("CREATE TABLE IF NOT EXISTS ") - quoter.QuoteTo(&b, tableName) + if err := quoter.QuoteTo(&b, tableName); err != nil { + return "", false, err + } b.WriteString(" (") for i, colName := range table.ColumnsSeq() { diff --git a/dialects/mysql.go b/dialects/mysql.go index 9cc695ef..1fad3fee 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -6,7 +6,6 @@ package dialects import ( "context" - "crypto/tls" "database/sql" "errors" "fmt" @@ -172,16 +171,7 @@ var ( type mysql struct { Base - net string - addr string - params map[string]string - loc *time.Location - timeout time.Duration - tls *tls.Config - allowAllFiles bool - allowOldPasswords bool - clientFoundRows bool - rowFormat string + rowFormat string } func (db *mysql) Init(uri *URI) error { @@ -497,15 +487,15 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName if _, ok := schemas.SqlTypes[colType]; ok { col.SQLType = schemas.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2} } else { - return nil, nil, fmt.Errorf("Unknown colType %v", colType) + return nil, nil, fmt.Errorf("unknown colType %v", colType) } if colKey == "PRI" { col.IsPrimaryKey = true } - if colKey == "UNI" { - // col.is - } + // if colKey == "UNI" { + // col.is + // } if extra == "auto_increment" { col.IsAutoIncrement = true @@ -785,7 +775,7 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { // Parse protocol part of URI p := strings.SplitN(pd[0], ":", 2) if len(p) != 2 { - return nil, errors.New("Wrong protocol part of URI") + return nil, errors.New("wrong protocol part of URI") } uri.Proto = p[0] options := strings.Split(p[1], ",") @@ -808,7 +798,7 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { } uri.Timeout = to default: - return nil, errors.New("Unknown option: " + k) + return nil, errors.New("unknown option: " + k) } } // Remove protocol part diff --git a/dialects/postgres.go b/dialects/postgres.go index 3595f6c5..76279d32 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1206,9 +1206,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A col.Default = "'" + col.Default + "'" } } else if col.SQLType.IsTime() { - if strings.HasSuffix(col.Default, "::timestamp without time zone") { - col.Default = strings.TrimSuffix(col.Default, "::timestamp without time zone") - } + col.Default = strings.TrimSuffix(col.Default, "::timestamp without time zone") } } cols[col.Name] = col @@ -1269,7 +1267,7 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN s := "SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1" if len(db.getSchema()) != 0 { args = append(args, db.getSchema()) - s = s + " AND schemaname=$2" + s += " AND schemaname=$2" } rows, err := queryer.QueryContext(ctx, s, args...) @@ -1337,13 +1335,13 @@ func (db *postgres) CreateTableSQL(ctx context.Context, queryer core.Queryer, ta return "", ok, err } - commentSql := "; " + commentSQL := "; " if table.Comment != "" { // support schema.table -> "schema"."table" - commentSql += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'", quoter.Quote(tableName), table.Comment) + commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'", quoter.Quote(tableName), table.Comment) } - return createTableSQL + commentSql, true, nil + return createTableSQL + commentSQL, true, nil } func (db *postgres) Filters() []Filter { diff --git a/dialects/table_name.go b/dialects/table_name.go index e190cd4b..48b44de2 100644 --- a/dialects/table_name.go +++ b/dialects/table_name.go @@ -17,8 +17,7 @@ import ( func TableNameWithSchema(dialect Dialect, tableName string) string { // Add schema name as prefix of table name. // Only for postgres database. - if dialect.URI().Schema != "" && - strings.Index(tableName, ".") == -1 { + if dialect.URI().Schema != "" && !strings.Contains(tableName, ".") { return fmt.Sprintf("%s.%s", dialect.URI().Schema, tableName) } return tableName @@ -27,20 +26,18 @@ func TableNameWithSchema(dialect Dialect, tableName string) string { // TableNameNoSchema returns table name with given tableName func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface{}) string { quote := dialect.Quoter().Quote - switch tableName.(type) { + switch tt := tableName.(type) { case []string: - t := tableName.([]string) - if len(t) > 1 { - return fmt.Sprintf("%v AS %v", quote(t[0]), quote(t[1])) - } else if len(t) == 1 { - return quote(t[0]) + if len(tt) > 1 { + return fmt.Sprintf("%v AS %v", quote(tt[0]), quote(tt[1])) + } else if len(tt) == 1 { + return quote(tt[0]) } case []interface{}: - t := tableName.([]interface{}) - l := len(t) + l := len(tt) var table string if l > 0 { - f := t[0] + f := tt[0] switch f.(type) { case string: table = f.(string) @@ -57,7 +54,7 @@ func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface } } if l > 1 { - return fmt.Sprintf("%v AS %v", quote(table), quote(fmt.Sprintf("%v", t[1]))) + return fmt.Sprintf("%v AS %v", quote(table), quote(fmt.Sprintf("%v", tt[1]))) } else if l == 1 { return quote(table) } diff --git a/engine.go b/engine.go index 7a57b08a..709cc384 100644 --- a/engine.go +++ b/engine.go @@ -467,7 +467,9 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w DBName: uri.DBName, Schema: uri.Schema, } - dstDialect.Init(&destURI) + if err := dstDialect.Init(&destURI); err != nil { + return err + } } cacherMgr := caches.NewManager() dstTableCache := tags.NewParser("xorm", dstDialect, engine.GetTableMapper(), engine.GetColumnMapper(), cacherMgr) @@ -588,7 +590,7 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w return err } } else if sess.engine.dialect.URI().DBType == schemas.DAMENG && stp.IsTime() && len(s.String) == 25 { - r := strings.Replace(s.String[:19], "T", " ", -1) + r := strings.ReplaceAll(s.String[:19], "T", " ") if _, err = io.WriteString(w, "'"+r+"'"); err != nil { return err } @@ -946,7 +948,7 @@ func (engine *Engine) CreateTables(beans ...interface{}) error { for _, bean := range beans { err = session.createTable(bean) if err != nil { - session.Rollback() + _ = session.Rollback() return err } } @@ -966,7 +968,7 @@ func (engine *Engine) DropTables(beans ...interface{}) error { for _, bean := range beans { err = session.dropTable(bean) if err != nil { - session.Rollback() + _ = session.Rollback() return err } } diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 74e1f903..dbe17571 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -58,12 +58,13 @@ func TestAutoTransaction(t *testing.T) { engine := testEngine.(*xorm.Engine) // will success - engine.Transaction(func(session *xorm.Session) (interface{}, error) { + _, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) { _, err := session.Insert(TestTx{Msg: "hi"}) assert.NoError(t, err) return nil, nil }) + assert.NoError(t, err) has, err := engine.Exist(&TestTx{Msg: "hi"}) assert.NoError(t, err) @@ -149,13 +150,14 @@ func TestDumpTables(t *testing.T) { assertSync(t, new(TestDumpTableStruct)) - testEngine.Insert([]TestDumpTableStruct{ + _, err := testEngine.Insert([]TestDumpTableStruct{ {Name: "1", IsMan: true}, {Name: "2\n"}, {Name: "3;"}, {Name: "4\n;\n''"}, {Name: "5'\n"}, }) + assert.NoError(t, err) fp := fmt.Sprintf("%v-table.sql", testEngine.Dialect().URI().DBType) os.Remove(fp) diff --git a/integrations/processors_test.go b/integrations/processors_test.go index b32f6fbb..4c383437 100644 --- a/integrations/processors_test.go +++ b/integrations/processors_test.go @@ -104,7 +104,7 @@ func (p *ProcessorsStruct) BeforeDelete() { } func (p *ProcessorsStruct) BeforeSet(col string, cell xorm.Cell) { - p.BeforeSetFlag = p.BeforeSetFlag + 1 + p.BeforeSetFlag++ } func (p *ProcessorsStruct) AfterInsert() { @@ -120,7 +120,7 @@ func (p *ProcessorsStruct) AfterDelete() { } func (p *ProcessorsStruct) AfterSet(col string, cell xorm.Cell) { - p.AfterSetFlag = p.AfterSetFlag + 1 + p.AfterSetFlag++ } func TestProcessors(t *testing.T) { diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 394e7e51..2495c1df 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -101,7 +101,7 @@ func callbackLooper(datas interface{}, step int, actionFunc func(interface{}) er if err = actionFunc(tempInterface); err != nil { return } - processedLen = processedLen - step + processedLen -= step } return } diff --git a/integrations/session_pk_test.go b/integrations/session_pk_test.go index e1aa8ed4..0244937f 100644 --- a/integrations/session_pk_test.go +++ b/integrations/session_pk_test.go @@ -121,7 +121,7 @@ func TestInt16Id(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(beans)) - beans2 := make(map[int16]Int16Id, 0) + beans2 := make(map[int16]Int16Id) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) @@ -154,7 +154,7 @@ func TestInt32Id(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(beans)) - beans2 := make(map[int32]Int32Id, 0) + beans2 := make(map[int32]Int32Id) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) @@ -205,7 +205,7 @@ func TestUintId(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 3, len(beans)) - beans2 := make(map[uint]UintId, 0) + beans2 := make(map[uint]UintId) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 3, len(beans2)) @@ -239,7 +239,7 @@ func TestUint16Id(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(beans)) - beans2 := make(map[uint16]Uint16Id, 0) + beans2 := make(map[uint16]Uint16Id) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) @@ -273,7 +273,7 @@ func TestUint32Id(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(beans)) - beans2 := make(map[uint32]Uint32Id, 0) + beans2 := make(map[uint32]Uint32Id) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) @@ -310,7 +310,7 @@ func TestUint64Id(t *testing.T) { assert.EqualValues(t, 1, len(beans)) assert.EqualValues(t, *bean, beans[0]) - beans2 := make(map[uint64]Uint64Id, 0) + beans2 := make(map[uint64]Uint64Id) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) @@ -533,7 +533,7 @@ func TestMyIntId(t *testing.T) { assert.EqualValues(t, 1, len(beans)) assert.EqualValues(t, *bean, beans[0]) - beans2 := make(map[ID]MyIntPK, 0) + beans2 := make(map[ID]MyIntPK) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) @@ -570,7 +570,7 @@ func TestMyStringId(t *testing.T) { assert.EqualValues(t, 1, len(beans)) assert.EqualValues(t, *bean, beans[0]) - beans2 := make(map[StrID]MyStringPK, 0) + beans2 := make(map[StrID]MyStringPK) err = testEngine.Find(&beans2) assert.NoError(t, err) assert.EqualValues(t, 1, len(beans2)) diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index c4ba4c7d..7dc0af76 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -328,7 +328,7 @@ func TestIsTableEmpty(t *testing.T) { Created time.Time `xorm:"created"` ILike int PageView int - From_url string + From_url string // nolint Pre_url string `xorm:"unique"` //pre view image's url Uid int64 } diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 30183382..45338cad 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -231,14 +231,12 @@ func TestForUpdate(t *testing.T) { f := new(ForUpdate) f.Name = "updated by session1" session1.Where("`id` = ?", 1) - session1.Update(f) + _, err = session1.Update(f) + assert.NoError(t, err) // release lock err = session1.Commit() - if err != nil { - t.Error(err) - return - } + assert.NoError(t, err) wg.Wait() } @@ -253,7 +251,7 @@ func TestWithIn(t *testing.T) { assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync(new(temp3))) - testEngine.Insert(&[]temp3{ + _, err := testEngine.Insert(&[]temp3{ { Name: "user1", }, @@ -264,6 +262,7 @@ func TestWithIn(t *testing.T) { Name: "user1", }, }) + assert.NoError(t, err) cnt, err := testEngine.In("Id", 1, 2, 3, 4).Update(&temp3{Name: "aa"}, &temp3{Name: "user1"}) assert.NoError(t, err) @@ -318,6 +317,7 @@ func TestUpdate1(t *testing.T) { _, err := testEngine.Insert(&Userinfo{ Username: "user1", }) + assert.NoError(t, err) var ori Userinfo has, err := testEngine.Get(&ori) @@ -925,6 +925,7 @@ func TestDeletedUpdate(t *testing.T) { var s1 DeletedUpdatedStruct has, err := testEngine.ID(s.Id).Get(&s1) + assert.NoError(t, err) assert.EqualValues(t, true, has) cnt, err = testEngine.ID(s.Id).Delete(&DeletedUpdatedStruct{}) @@ -941,6 +942,7 @@ func TestDeletedUpdate(t *testing.T) { var s2 DeletedUpdatedStruct has, err = testEngine.ID(s.Id).Get(&s2) + assert.NoError(t, err) assert.EqualValues(t, true, has) } diff --git a/integrations/tags_test.go b/integrations/tags_test.go index c05a8d99..247a64e8 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -1202,7 +1202,7 @@ func TestTagTime(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"), - strings.Replace(strings.Replace(tm, "T", " ", -1), "Z", "", -1)) + strings.ReplaceAll(strings.ReplaceAll(tm, "T", " "), "Z", "")) } func TestTagAutoIncr(t *testing.T) { @@ -1287,7 +1287,7 @@ func TestVersion1(t *testing.T) { assert.EqualValues(t, newVer.Ver, 2) newVer = new(VersionS) - has, err = testEngine.ID(ver.Id).Get(newVer) + _, err = testEngine.ID(ver.Id).Get(newVer) assert.NoError(t, err) assert.EqualValues(t, newVer.Ver, 2) } @@ -1345,7 +1345,7 @@ func TestVersion3(t *testing.T) { assert.EqualValues(t, newVer.Ver, 2) newVer = new(VersionUintS) - has, err = testEngine.ID(ver.Id).Get(newVer) + _, err = testEngine.ID(ver.Id).Get(newVer) assert.NoError(t, err) assert.EqualValues(t, newVer.Ver, 2) } diff --git a/integrations/tests.go b/integrations/tests.go index 8b14b0f4..59f4b29a 100644 --- a/integrations/tests.go +++ b/integrations/tests.go @@ -51,7 +51,7 @@ func createEngine(dbType, connStr string) error { if !*cluster { switch schemas.DBType(strings.ToLower(dbType)) { case schemas.MSSQL: - db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "master", -1)) + db, err := sql.Open(dbType, strings.ReplaceAll(connStr, "xorm_test", "master")) if err != nil { return err } @@ -61,7 +61,7 @@ func createEngine(dbType, connStr string) error { db.Close() *ignoreSelectUpdate = true case schemas.POSTGRES: - db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "postgres", -1)) + db, err := sql.Open(dbType, strings.ReplaceAll(connStr, "xorm_test", "postgres")) if err != nil { return err } @@ -90,7 +90,7 @@ func createEngine(dbType, connStr string) error { db.Close() *ignoreSelectUpdate = true case schemas.MYSQL: - db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "mysql", -1)) + db, err := sql.Open(dbType, strings.ReplaceAll(connStr, "xorm_test", "mysql")) if err != nil { return err } diff --git a/internal/statements/query.go b/internal/statements/query.go index dd38a9a2..16253417 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -79,7 +79,9 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri return statement.GenRawSQL(), statement.RawParams, nil } - statement.SetRefBean(bean) + if err := statement.SetRefBean(bean); err != nil { + return "", nil, err + } var sumStrs = make([]string, 0, len(columns)) for _, colName := range columns { @@ -111,7 +113,9 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, v := rValue(bean) isStruct = v.Kind() == reflect.Struct if isStruct { - statement.SetRefBean(bean) + if err := statement.SetRefBean(bean); err != nil { + return "", nil, err + } } } @@ -168,7 +172,9 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa var condArgs []interface{} var err error if len(beans) > 0 { - statement.SetRefBean(beans[0]) + if err := statement.SetRefBean(beans[0]); err != nil { + return "", nil, err + } if err := statement.mergeConds(beans[0]); err != nil { return "", nil, err } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index c9882a47..2a7ae8b0 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -473,7 +473,7 @@ func (statement *Statement) Desc(colNames ...string) *Statement { if i > 0 { fmt.Fprint(&buf, ", ") } - statement.dialect.Quoter().QuoteTo(&buf, col) + _ = statement.dialect.Quoter().QuoteTo(&buf, col) fmt.Fprint(&buf, " DESC") } statement.OrderStr = buf.String() @@ -490,7 +490,7 @@ func (statement *Statement) Asc(colNames ...string) *Statement { if i > 0 { fmt.Fprint(&buf, ", ") } - statement.dialect.Quoter().QuoteTo(&buf, col) + _ = statement.dialect.Quoter().QuoteTo(&buf, col) fmt.Fprint(&buf, " ASC") } statement.OrderStr = buf.String() @@ -558,7 +558,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) if !utils.IsSubQuery(tbName) { var buf strings.Builder - statement.dialect.Quoter().QuoteTo(&buf, tbName) + _ = statement.dialect.Quoter().QuoteTo(&buf, tbName) tbName = buf.String() } else { tbName = statement.ReplaceQuote(tbName) @@ -571,15 +571,6 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition return statement } -// tbNameNoSchema get some table's table name -func (statement *Statement) tbNameNoSchema(table *schemas.Table) string { - if len(statement.AltTableName) > 0 { - return statement.AltTableName - } - - return table.Name -} - // GroupBy generate "Group By keys" statement func (statement *Statement) GroupBy(keys string) *Statement { statement.GroupByStr = statement.ReplaceQuote(keys) @@ -857,9 +848,6 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, fieldValuePtr, err := col.ValueOf(bean) if err != nil { - if !strings.Contains(err.Error(), "is not valid") { - //engine.logger.Warn(err) - } continue } else if fieldValuePtr == nil { continue diff --git a/internal/statements/statement_test.go b/internal/statements/statement_test.go index abe0e0f2..31428efa 100644 --- a/internal/statements/statement_test.go +++ b/internal/statements/statement_test.go @@ -5,6 +5,7 @@ package statements import ( + "os" "reflect" "strings" "testing" @@ -37,6 +38,7 @@ func TestMain(m *testing.M) { panic("tags parser is nil") } m.Run() + os.Exit(0) } var colStrTests = []struct { diff --git a/internal/utils/slice.go b/internal/utils/slice.go index 06a1a006..82289b1a 100644 --- a/internal/utils/slice.go +++ b/internal/utils/slice.go @@ -21,6 +21,7 @@ func SliceEq(left, right []string) bool { return true } +// IndexSlice search c in slice s and return the index, return -1 if s don't contain c func IndexSlice(s []string, c string) int { for i, ss := range s { if c == ss { diff --git a/log/logger.go b/log/logger.go index 3b6db34e..b8798c3f 100644 --- a/log/logger.go +++ b/log/logger.go @@ -130,56 +130,56 @@ func NewSimpleLogger3(out io.Writer, prefix string, flag int, l LogLevel) *Simpl // Error implement ILogger func (s *SimpleLogger) Error(v ...interface{}) { if s.level <= LOG_ERR { - s.ERR.Output(2, fmt.Sprintln(v...)) + _ = s.ERR.Output(2, fmt.Sprintln(v...)) } } // Errorf implement ILogger func (s *SimpleLogger) Errorf(format string, v ...interface{}) { if s.level <= LOG_ERR { - s.ERR.Output(2, fmt.Sprintf(format, v...)) + _ = s.ERR.Output(2, fmt.Sprintf(format, v...)) } } // Debug implement ILogger func (s *SimpleLogger) Debug(v ...interface{}) { if s.level <= LOG_DEBUG { - s.DEBUG.Output(2, fmt.Sprintln(v...)) + _ = s.DEBUG.Output(2, fmt.Sprintln(v...)) } } // Debugf implement ILogger func (s *SimpleLogger) Debugf(format string, v ...interface{}) { if s.level <= LOG_DEBUG { - s.DEBUG.Output(2, fmt.Sprintf(format, v...)) + _ = s.DEBUG.Output(2, fmt.Sprintf(format, v...)) } } // Info implement ILogger func (s *SimpleLogger) Info(v ...interface{}) { if s.level <= LOG_INFO { - s.INFO.Output(2, fmt.Sprintln(v...)) + _ = s.INFO.Output(2, fmt.Sprintln(v...)) } } // Infof implement ILogger func (s *SimpleLogger) Infof(format string, v ...interface{}) { if s.level <= LOG_INFO { - s.INFO.Output(2, fmt.Sprintf(format, v...)) + _ = s.INFO.Output(2, fmt.Sprintf(format, v...)) } } // Warn implement ILogger func (s *SimpleLogger) Warn(v ...interface{}) { if s.level <= LOG_WARNING { - s.WARN.Output(2, fmt.Sprintln(v...)) + _ = s.WARN.Output(2, fmt.Sprintln(v...)) } } // Warnf implement ILogger func (s *SimpleLogger) Warnf(format string, v ...interface{}) { if s.level <= LOG_WARNING { - s.WARN.Output(2, fmt.Sprintf(format, v...)) + _ = s.WARN.Output(2, fmt.Sprintf(format, v...)) } } diff --git a/log/syslogger.go b/log/syslogger.go index 0b3e381c..44272586 100644 --- a/log/syslogger.go +++ b/log/syslogger.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !windows && !nacl && !plan9 // +build !windows,!nacl,!plan9 package log @@ -26,42 +27,42 @@ func NewSyslogLogger(w *syslog.Writer) *SyslogLogger { // Debug log content as Debug func (s *SyslogLogger) Debug(v ...interface{}) { - s.w.Debug(fmt.Sprint(v...)) + _ = s.w.Debug(fmt.Sprint(v...)) } // Debugf log content as Debug and format func (s *SyslogLogger) Debugf(format string, v ...interface{}) { - s.w.Debug(fmt.Sprintf(format, v...)) + _ = s.w.Debug(fmt.Sprintf(format, v...)) } // Error log content as Error func (s *SyslogLogger) Error(v ...interface{}) { - s.w.Err(fmt.Sprint(v...)) + _ = s.w.Err(fmt.Sprint(v...)) } // Errorf log content as Errorf and format func (s *SyslogLogger) Errorf(format string, v ...interface{}) { - s.w.Err(fmt.Sprintf(format, v...)) + _ = s.w.Err(fmt.Sprintf(format, v...)) } // Info log content as Info func (s *SyslogLogger) Info(v ...interface{}) { - s.w.Info(fmt.Sprint(v...)) + _ = s.w.Info(fmt.Sprint(v...)) } // Infof log content as Infof and format func (s *SyslogLogger) Infof(format string, v ...interface{}) { - s.w.Info(fmt.Sprintf(format, v...)) + _ = s.w.Info(fmt.Sprintf(format, v...)) } // Warn log content as Warn func (s *SyslogLogger) Warn(v ...interface{}) { - s.w.Warning(fmt.Sprint(v...)) + _ = s.w.Warning(fmt.Sprint(v...)) } // Warnf log content as Warnf and format func (s *SyslogLogger) Warnf(format string, v ...interface{}) { - s.w.Warning(fmt.Sprintf(format, v...)) + _ = s.w.Warning(fmt.Sprintf(format, v...)) } // Level shows log level diff --git a/migrate/migrate.go b/migrate/migrate.go index 19b4afe0..5c259627 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -203,7 +203,7 @@ func (m *Migrate) migrationDidRun(mig *Migration) (bool, error) { func (m *Migrate) isFirstRun() bool { row := m.db.DB().QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM %s", m.options.TableName)) var count int - row.Scan(&count) + _ = row.Scan(&count) return count == 0 } diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 8f15a025..750afb28 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -142,6 +142,6 @@ func TestMissingID(t *testing.T) { func tableCount(db *xorm.Engine, tableName string) (count int) { row := db.DB().QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName)) - row.Scan(&count) + _ = row.Scan(&count) return } diff --git a/rows.go b/rows.go index 110f41b6..4801c300 100644 --- a/rows.go +++ b/rows.go @@ -40,7 +40,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { return nil, err } - if len(session.statement.TableName()) <= 0 { + if len(session.statement.TableName()) == 0 { return nil, ErrTableNotFound } diff --git a/scan.go b/scan.go index d562abbc..10988bdb 100644 --- a/scan.go +++ b/scan.go @@ -235,6 +235,7 @@ func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, return resultsMap, nil } +// ScanInterfaceMap scan result from *core.Rows and return a map func (engine *Engine) ScanInterfaceMap(rows *core.Rows) (map[string]interface{}, error) { fields, err := rows.Columns() if err != nil { @@ -248,6 +249,7 @@ func (engine *Engine) ScanInterfaceMap(rows *core.Rows) (map[string]interface{}, return engine.row2mapInterface(rows, types, fields) } +// ScanInterfaceMaps scan results from *core.Rows and return a slice of map func (engine *Engine) ScanInterfaceMaps(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) { fields, err := rows.Columns() if err != nil { @@ -309,6 +311,7 @@ func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, field return result, nil } +// ScanStringMap scan results from *core.Rows and return a map func (engine *Engine) ScanStringMap(rows *core.Rows) (map[string]string, error) { fields, err := rows.Columns() if err != nil { @@ -321,6 +324,7 @@ func (engine *Engine) ScanStringMap(rows *core.Rows) (map[string]string, error) return engine.row2mapStr(rows, types, fields) } +// ScanStringMaps scan results from *core.Rows and return a slice of map func (engine *Engine) ScanStringMaps(rows *core.Rows) (resultsSlice []map[string]string, err error) { fields, err := rows.Columns() if err != nil { @@ -395,6 +399,7 @@ func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fie return results, nil } +// ScanStringSlice scan results from *core.Rows and return a slice of one row func (engine *Engine) ScanStringSlice(rows *core.Rows) ([]string, error) { fields, err := rows.Columns() if err != nil { @@ -408,6 +413,7 @@ func (engine *Engine) ScanStringSlice(rows *core.Rows) ([]string, error) { return engine.row2sliceStr(rows, types, fields) } +// ScanStringSlices scan results from *core.Rows and return a slice of all rows func (engine *Engine) ScanStringSlices(rows *core.Rows) (resultsSlice [][]string, err error) { fields, err := rows.Columns() if err != nil { diff --git a/schemas/index.go b/schemas/index.go index 8f31af52..47027ea4 100644 --- a/schemas/index.go +++ b/schemas/index.go @@ -32,7 +32,7 @@ func NewIndex(name string, indexType int) *Index { func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { - tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") + tableParts := strings.Split(strings.ReplaceAll(tableName, `"`, ""), ".") tableName = tableParts[len(tableParts)-1] if index.Type == UniqueType { return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) diff --git a/schemas/quote.go b/schemas/quote.go index 71040ad9..4cab30fe 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -37,7 +37,7 @@ func (q Quoter) IsEmpty() bool { // Quote quote a string func (q Quoter) Quote(s string) string { var buf strings.Builder - q.QuoteTo(&buf, s) + _ = q.QuoteTo(&buf, s) return buf.String() } @@ -64,7 +64,7 @@ func (q Quoter) Trim(s string) string { // Join joins a slice with quoters func (q Quoter) Join(a []string, sep string) string { var b strings.Builder - q.JoinWrite(&b, a, sep) + _ = q.JoinWrite(&b, a, sep) return b.String() } @@ -86,7 +86,9 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error { return err } } - q.QuoteTo(b, strings.TrimSpace(s)) + if err := q.QuoteTo(b, strings.TrimSpace(s)); err != nil { + return err + } } return nil } @@ -121,7 +123,7 @@ func findStart(value string, start int) int { } if (value[k] == 'A' || value[k] == 'a') && (value[k+1] == 'S' || value[k+1] == 's') { - k = k + 2 + k += 2 } for j := k; j < len(value); j++ { diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 061a6ea2..f84dfb7d 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -45,7 +45,8 @@ func TestAlwaysQuoteTo(t *testing.T) { for _, v := range kases { t.Run(v.value, func(t *testing.T) { buf := &strings.Builder{} - quoter.QuoteTo(buf, v.value) + err := quoter.QuoteTo(buf, v.value) + assert.NoError(t, err) assert.EqualValues(t, v.expected, buf.String()) }) } @@ -54,10 +55,7 @@ func TestAlwaysQuoteTo(t *testing.T) { func TestReversedQuoteTo(t *testing.T) { var ( quoter = Quoter{'[', ']', func(s string) bool { - if s == "mytable" { - return true - } - return false + return s == "mytable" }} kases = []struct { expected string @@ -118,7 +116,8 @@ func TestNoQuoteTo(t *testing.T) { for _, v := range kases { t.Run(v.value, func(t *testing.T) { buf := &strings.Builder{} - quoter.QuoteTo(buf, v.value) + err := quoter.QuoteTo(buf, v.value) + assert.NoError(t, err) assert.EqualValues(t, v.expected, buf.String()) }) } diff --git a/session.go b/session.go index 2c916335..21bbe6e1 100644 --- a/session.go +++ b/session.go @@ -704,7 +704,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b if idx, ok = tempMap[lKey]; !ok { idx = 0 } else { - idx = idx + 1 + idx++ } tempMap[lKey] = idx diff --git a/session_delete.go b/session_delete.go index 9c19e43e..a0f420b1 100644 --- a/session_delete.go +++ b/session_delete.go @@ -232,7 +232,7 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } if cacher := session.engine.GetCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache { - session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) + _ = session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) } session.statement.RefTable = table diff --git a/session_find.go b/session_find.go index 47a3d308..dcac93b7 100644 --- a/session_find.go +++ b/session_find.go @@ -71,7 +71,11 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte } // session has stored the conditions so we use `unscoped` to avoid duplicated condition. - return session.Unscoped().Count(reflect.New(sliceElementType).Interface()) + if sliceElementType.Kind() == reflect.Struct { + return session.Unscoped().Count(reflect.New(sliceElementType).Interface()) + } + + return session.Unscoped().Count() } func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error { @@ -152,7 +156,6 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) if err != ErrCacheFailed { return err } - err = nil // !nashtsai! reset err to nil for ErrCacheFailed session.engine.logger.Warnf("Cache Find Failed") } } diff --git a/session_get.go b/session_get.go index 48616a6b..9bb92a8b 100644 --- a/session_get.go +++ b/session_get.go @@ -6,7 +6,6 @@ package xorm import ( "database/sql" - "database/sql/driver" "errors" "fmt" "math/big" @@ -79,7 +78,7 @@ func (session *Session) get(beans ...interface{}) (bool, error) { var err error if session.statement.RawSQL == "" { - if len(session.statement.TableName()) <= 0 { + if len(session.statement.TableName()) == 0 { return false, ErrTableNotFound } session.statement.Limit(1) @@ -130,14 +129,6 @@ func (session *Session) get(beans ...interface{}) (bool, error) { return true, nil } -var ( - valuerTypePlaceHolder driver.Valuer - valuerType = reflect.TypeOf(&valuerTypePlaceHolder).Elem() - - conversionTypePlaceHolder convert.Conversion - conversionType = reflect.TypeOf(&conversionTypePlaceHolder).Elem() -) - func isScannableStruct(bean interface{}, typeLen int) bool { switch bean.(type) { case *time.Time: diff --git a/session_insert.go b/session_insert.go index 43a4118b..4835eb14 100644 --- a/session_insert.go +++ b/session_insert.go @@ -80,7 +80,7 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e } tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } @@ -90,7 +90,6 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e colNames []string colMultiPlaces []string args []interface{} - cols []*schemas.Column ) for i := 0; i < size; i++ { @@ -172,7 +171,6 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e if i == 0 { colNames = append(colNames, col.Name) - cols = append(cols, col) } colPlaces = append(colPlaces, "?") } @@ -203,7 +201,7 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e return 0, err } - session.cacheInsert(tableName) + _ = session.cacheInsert(tableName) lenAfterClosures := len(session.afterClosures) for i := 0; i < size; i++ { @@ -257,7 +255,7 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { if err := session.statement.SetRefBean(bean); err != nil { return 0, err } - if len(session.statement.TableName()) <= 0 { + if len(session.statement.TableName()) == 0 { return 0, ErrTableNotFound } @@ -360,7 +358,7 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { defer handleAfterInsertProcessorFunc(bean) - session.cacheInsert(tableName) + _ = session.cacheInsert(tableName) if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) @@ -390,7 +388,7 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { defer handleAfterInsertProcessorFunc(bean) - session.cacheInsert(tableName) + _ = session.cacheInsert(tableName) if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) @@ -430,6 +428,7 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { // InsertOne insert only one struct into database as a record. // The in parameter bean must a struct or a point to struct. The return // parameter is inserted and error +// Deprecated: Please use Insert directly func (session *Session) InsertOne(bean interface{}) (int64, error) { if session.isAutoClose { defer session.Close() @@ -537,7 +536,7 @@ func (session *Session) insertMapInterface(m map[string]interface{}) (int64, err } tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } @@ -559,12 +558,12 @@ func (session *Session) insertMapInterface(m map[string]interface{}) (int64, err } func (session *Session) insertMultipleMapInterface(maps []map[string]interface{}) (int64, error) { - if len(maps) <= 0 { + if len(maps) == 0 { return 0, ErrNoElementsOnSlice } tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } @@ -595,7 +594,7 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { } tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } @@ -618,12 +617,12 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { } func (session *Session) insertMultipleMapString(maps []map[string]string) (int64, error) { - if len(maps) <= 0 { + if len(maps) == 0 { return 0, ErrNoElementsOnSlice } tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } @@ -650,7 +649,7 @@ func (session *Session) insertMultipleMapString(maps []map[string]string) (int64 func (session *Session) insertMap(columns []string, args []interface{}) (int64, error) { tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } @@ -677,7 +676,7 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, func (session *Session) insertMultipleMap(columns []string, argss [][]interface{}) (int64, error) { tableName := session.statement.TableName() - if len(tableName) <= 0 { + if len(tableName) == 0 { return 0, ErrTableNotFound } diff --git a/session_iterate.go b/session_iterate.go index f6301009..afb9a7cc 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -95,7 +95,7 @@ func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { break } - start = start + slice.Elem().Len() + start += slice.Elem().Len() if pLimitN != nil && start+bufferSize > *pLimitN { bufferSize = *pLimitN - start } diff --git a/session_schema.go b/session_schema.go index 75140426..e66c3b42 100644 --- a/session_schema.go +++ b/session_schema.go @@ -223,24 +223,6 @@ func (session *Session) isTableEmpty(tableName string) (bool, error) { return total == 0, nil } -// find if index is exist according cols -func (session *Session) isIndexExist2(tableName string, cols []string, unique bool) (bool, error) { - indexes, err := session.engine.dialect.GetIndexes(session.getQueryer(), session.ctx, tableName) - if err != nil { - return false, err - } - - for _, index := range indexes { - if utils.SliceEq(index.Cols, cols) { - if unique { - return index.Type == schemas.UniqueType, nil - } - return index.Type == schemas.IndexType, nil - } - } - return false, nil -} - func (session *Session) addColumn(colName string) error { col := session.statement.RefTable.GetColumn(colName) sql := session.engine.dialect.AddColumnSQL(session.statement.TableName(), col) diff --git a/session_tx.go b/session_tx.go index 8763784c..4fa56891 100644 --- a/session_tx.go +++ b/session_tx.go @@ -75,7 +75,7 @@ func (session *Session) Commit() error { } cleanUpFunc := func(slices *map[interface{}]*[]func(interface{})) { if len(*slices) > 0 { - *slices = make(map[interface{}]*[]func(interface{}), 0) + *slices = make(map[interface{}]*[]func(interface{})) } } cleanUpFunc(&session.afterInsertBeans) diff --git a/session_update.go b/session_update.go index 4fd45a53..fefbee90 100644 --- a/session_update.go +++ b/session_update.go @@ -22,6 +22,7 @@ var ( ErrNoColumnsTobeUpdated = errors.New("no columns found to be updated") ) +//revive:disable func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { @@ -39,7 +40,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri var nStart int if len(args) > 0 { - if strings.Index(sqlStr, "?") > -1 { + if strings.Contains(sqlStr, "?") { nStart = strings.Count(oldhead, "?") } else { // only for pq, TODO: if any other databse? @@ -182,7 +183,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 return 0, err } - if len(session.statement.TableName()) <= 0 { + if len(session.statement.TableName()) == 0 { return 0, ErrTableNotFound } @@ -342,7 +343,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - if len(colNames) <= 0 { + if len(colNames) == 0 { return 0, ErrNoColumnsTobeUpdated } @@ -356,7 +357,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } if st.OrderStr != "" { - condSQL = condSQL + fmt.Sprintf(" ORDER BY %v", st.OrderStr) + condSQL += fmt.Sprintf(" ORDER BY %v", st.OrderStr) } var tableName = session.statement.TableName() @@ -366,7 +367,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 limitValue := *st.LimitN switch session.engine.dialect.URI().DBType { case schemas.MYSQL: - condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) + condSQL += fmt.Sprintf(" LIMIT %d", limitValue) case schemas.SQLITE: tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", From 57365108ae98aefd57a732f0e2e65fa38f865798 Mon Sep 17 00:00:00 2001 From: satorunooshie Date: Mon, 27 Dec 2021 10:11:44 +0800 Subject: [PATCH 142/179] Fix insertMultipleStruct to insert null value under certain circumstances (#2077) The behavior of multi insertion of null differs from that of single insertion. On the other hand, behavior between single and bulk insertion of null using pointer of struct field is identical. Please see the example below. ```go s := engineGroup.NewSession() type User struct { ID int `xorm:"not null pk autoincr INT(10)"` Name string `xorm:"not null VARCHAR(191)"` Age int64 `xorm:"null BIGINT(20)"` } list := []*User{ { Name: "John", }, { Name: "Wick", }, } s.Nullable("age") // Single insertion works _, err := s.Insert(list[0]) // [table] `user` INSERT INTO `user` (`name`,`age`) VALUES (?, ?) [John ] s.Nullable("age") // Bulk insertion does not work _, err := s.Insert(list) // [table] `user` INSERT INTO `user` (`name,`age`) VALUES (?, ?),(?, ?) [John, 0, Wick, 0] //--------------------------------- //Using pointer, which is nullable, the generated sql has no difference. //--------------------------------- type User struct { ID int `xorm:"not null pk autoincr INT(10)"` Name string `xorm:"not null VARCHAR(191)"` Age *int64 `xorm:"null BIGINT(20)"` } list := []*User{ { Name: "John", }, { Name: "Wick", }, } s.Nullable("age") // Single insertion works _, err := s.Insert(list[0]) // [table] `user` INSERT INTO `user` (`name`,`age`) VALUES (?, ?) [John ] s.Nullable("age") // Bulk insertion does not work _, err := s.Insert(list) // [table] `user` INSERT INTO `user` (`name,`age`) VALUES (?, ?),(?, ?) [John, , Wick, ] ``` Reviewed-on: https://gitea.com/xorm/xorm/pulls/2077 Co-authored-by: satorunooshie Co-committed-by: satorunooshie --- session_insert.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/session_insert.go b/session_insert.go index 4835eb14..fc025613 100644 --- a/session_insert.go +++ b/session_insert.go @@ -142,6 +142,13 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { continue } + // !satorunooshie! set fieldValue as nil when column is nullable and zero-value + if _, ok := getFlagForColumn(session.statement.NullableMap, col); ok { + if col.Nullable && utils.IsValueZero(fieldValue) { + var nilValue *int + fieldValue = reflect.ValueOf(nilValue) + } + } if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { val, t, err := session.engine.nowTime(col) if err != nil { From 470807151d0a443bcdd49a13d3ec108e1eb65bd2 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Wed, 5 Jan 2022 09:37:18 +0800 Subject: [PATCH 143/179] fix cross db dumping of bools (#2089) When dumping booleans these need to be converted from the original DB representation to the new db representation. In the case of most DBs this is simply to 0 or 1 but for postgres these have to be false or true. Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/2089 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- engine.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/engine.go b/engine.go index 709cc384..1257de20 100644 --- a/engine.go +++ b/engine.go @@ -12,6 +12,7 @@ import ( "os" "reflect" "runtime" + "strconv" "strings" "time" @@ -438,16 +439,14 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return engine.dumpTables(context.Background(), tables, w, tp...) } -func formatBool(s string, dstDialect dialects.Dialect) string { - if dstDialect.URI().DBType == schemas.MSSQL { - switch s { - case "true": +func formatBool(s bool, dstDialect dialects.Dialect) string { + if dstDialect.URI().DBType != schemas.POSTGRES { + if s { return "1" - case "false": - return "0" } + return "0" } - return s + return strconv.FormatBool(s) } // dumpTables dump database all table structs and data to w with specify db type @@ -581,8 +580,13 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w return err } } else { - if stp.IsBool() || (dstDialect.URI().DBType == schemas.MSSQL && strings.EqualFold(stp.Name, schemas.Bit)) { - if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil { + if table.Columns()[i].SQLType.IsBool() || stp.IsBool() || (dstDialect.URI().DBType == schemas.MSSQL && strings.EqualFold(stp.Name, schemas.Bit)) { + val, err := strconv.ParseBool(s.String) + if err != nil { + return err + } + + if _, err = io.WriteString(w, formatBool(val, dstDialect)); err != nil { return err } } else if stp.IsNumeric() { From 2fa71307041bd5c181cd37f48492bdc4620d6674 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 6 Jan 2022 19:43:06 +0800 Subject: [PATCH 144/179] Improve find interface (#2092) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2092 --- session_find.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/session_find.go b/session_find.go index dcac93b7..57ac7bb7 100644 --- a/session_find.go +++ b/session_find.go @@ -254,9 +254,9 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect switch elemType.Kind() { case reflect.Slice: - err = rows.ScanSlice(bean) + err = session.getSlice(rows, types, fields, bean) case reflect.Map: - err = rows.ScanMap(bean) + err = session.getMap(rows, types, fields, bean) default: err = rows.Scan(bean) } From cd36b112ae482f4fa9f0c590e103e32211a2ad80 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 7 Jan 2022 16:04:01 +0800 Subject: [PATCH 145/179] Escape string and blob results from dump more correctly (#2091) dumpTables currently badly handles BLOB and TEXT data containing control characters: * MySQL will interpret and unescape string literals e.g.`\r` will become carriage return. * Postgres will not allow string literals to contain NUL nor will SQLite so BLOBs will not dump correctly. * Schemas should not be set on the destination dump * MSSQL needs the N prefix to correctly ensure that UTF-8 data is correctly transferred. Signed-off-by: Andrew Thornton Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2091 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- engine.go | 191 +++++++++++++++++++++++++++++++++++- integrations/engine_test.go | 13 ++- 2 files changed, 199 insertions(+), 5 deletions(-) diff --git a/engine.go b/engine.go index 1257de20..b7dcf5a2 100644 --- a/engine.go +++ b/engine.go @@ -11,6 +11,7 @@ import ( "io" "os" "reflect" + "regexp" "runtime" "strconv" "strings" @@ -449,6 +450,8 @@ func formatBool(s bool, dstDialect dialects.Dialect) string { return strconv.FormatBool(s) } +var controlCharactersRe = regexp.MustCompile(`[\x00-\x1f\x7f]+`) + // dumpTables dump database all table structs and data to w with specify db type func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { var dstDialect dialects.Dialect @@ -464,7 +467,10 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w destURI := dialects.URI{ DBType: tp[0], DBName: uri.DBName, - Schema: uri.Schema, + // DO NOT SET SCHEMA HERE + } + if tp[0] == schemas.POSTGRES { + destURI.Schema = engine.dialect.URI().Schema } if err := dstDialect.Init(&destURI); err != nil { return err @@ -479,6 +485,13 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w return err } + if dstDialect.URI().DBType == schemas.MYSQL { + // For MySQL set NO_BACKLASH_ESCAPES so that strings work properly + if _, err := io.WriteString(w, "SET sql_mode='NO_BACKSLASH_ESCAPES';\n"); err != nil { + return err + } + } + for i, table := range tables { dstTable := table if table.Type != nil { @@ -598,6 +611,182 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w if _, err = io.WriteString(w, "'"+r+"'"); err != nil { return err } + } else if len(s.String) == 0 { + if _, err := io.WriteString(w, "''"); err != nil { + return err + } + } else if dstDialect.URI().DBType == schemas.POSTGRES { + if dstTable.Columns()[i].SQLType.IsBlob() { + // Postgres has the escape format and we should use that for bytea data + if _, err := fmt.Fprintf(w, "'\\x%x'", s.String); err != nil { + return err + } + } else { + // Postgres concatentates strings using || (NOTE: a NUL byte in a text segment will fail) + toCheck := strings.ReplaceAll(s.String, "'", "''") + for len(toCheck) > 0 { + loc := controlCharactersRe.FindStringIndex(toCheck) + if loc == nil { + if _, err := io.WriteString(w, "'"+toCheck+"'"); err != nil { + return err + } + break + } + if loc[0] > 0 { + if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"' || "); err != nil { + return err + } + } + if _, err := io.WriteString(w, "e'"); err != nil { + return err + } + for i := loc[0]; i < loc[1]; i++ { + if _, err := fmt.Fprintf(w, "\\x%02x", toCheck[i]); err != nil { + return err + } + } + toCheck = toCheck[loc[1]:] + if len(toCheck) > 0 { + if _, err := io.WriteString(w, "' || "); err != nil { + return err + } + } else { + if _, err := io.WriteString(w, "'"); err != nil { + return err + } + } + } + } + } else if dstDialect.URI().DBType == schemas.MYSQL { + loc := controlCharactersRe.FindStringIndex(s.String) + if loc == nil { + if _, err := io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { + return err + } + } else { + if _, err := io.WriteString(w, "CONCAT("); err != nil { + return err + } + toCheck := strings.ReplaceAll(s.String, "'", "''") + for len(toCheck) > 0 { + loc := controlCharactersRe.FindStringIndex(toCheck) + if loc == nil { + if _, err := io.WriteString(w, "'"+toCheck+"')"); err != nil { + return err + } + break + } + if loc[0] > 0 { + if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"', "); err != nil { + return err + } + } + for i := loc[0]; i < loc[1]-1; i++ { + if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(toCheck[i]))+"), "); err != nil { + return err + } + } + char := toCheck[loc[1]-1] + toCheck = toCheck[loc[1]:] + if len(toCheck) > 0 { + if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"), "); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"))"); err != nil { + return err + } + } + } + } + } else if dstDialect.URI().DBType == schemas.SQLITE { + if dstTable.Columns()[i].SQLType.IsBlob() { + // SQLite has its escape format + if _, err := fmt.Fprintf(w, "X'%x'", s.String); err != nil { + return err + } + } else { + // SQLite concatentates strings using || (NOTE: a NUL byte in a text segment will fail) + toCheck := strings.ReplaceAll(s.String, "'", "''") + for len(toCheck) > 0 { + loc := controlCharactersRe.FindStringIndex(toCheck) + if loc == nil { + if _, err := io.WriteString(w, "'"+toCheck+"'"); err != nil { + return err + } + break + } + if loc[0] > 0 { + if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"' || "); err != nil { + return err + } + } + if _, err := fmt.Fprintf(w, "X'%x'", toCheck[loc[0]:loc[1]]); err != nil { + return err + } + toCheck = toCheck[loc[1]:] + if len(toCheck) > 0 { + if _, err := io.WriteString(w, " || "); err != nil { + return err + } + } + } + } + } else if dstDialect.URI().DBType == schemas.DAMENG || dstDialect.URI().DBType == schemas.ORACLE { + if dstTable.Columns()[i].SQLType.IsBlob() { + // ORACLE/DAMENG uses HEXTORAW + if _, err := fmt.Fprintf(w, "HEXTORAW('%x')", s.String); err != nil { + return err + } + } else { + // ORACLE/DAMENG concatentates strings in multiple ways but uses CHAR and has CONCAT + // (NOTE: a NUL byte in a text segment will fail) + if _, err := io.WriteString(w, "CONCAT("); err != nil { + return err + } + toCheck := strings.ReplaceAll(s.String, "'", "''") + for len(toCheck) > 0 { + loc := controlCharactersRe.FindStringIndex(toCheck) + if loc == nil { + if _, err := io.WriteString(w, "'"+toCheck+"')"); err != nil { + return err + } + break + } + if loc[0] > 0 { + if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"', "); err != nil { + return err + } + } + for i := loc[0]; i < loc[1]-1; i++ { + if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(toCheck[i]))+"), "); err != nil { + return err + } + } + char := toCheck[loc[1]-1] + toCheck = toCheck[loc[1]:] + if len(toCheck) > 0 { + if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"), "); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"))"); err != nil { + return err + } + } + } + } + } else if dstDialect.URI().DBType == schemas.MSSQL { + if dstTable.Columns()[i].SQLType.IsBlob() { + // MSSQL uses CONVERT(VARBINARY(MAX), '0xDEADBEEF', 1) + if _, err := fmt.Fprintf(w, "CONVERT(VARBINARY(MAX), '0x%x', 1)", s.String); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "N'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { + return err + } + } } else { if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { return err diff --git a/integrations/engine_test.go b/integrations/engine_test.go index dbe17571..cdcdd6be 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -143,6 +143,7 @@ func TestDumpTables(t *testing.T) { type TestDumpTableStruct struct { Id int64 + Data []byte `xorm:"BLOB"` Name string IsMan bool Created time.Time `xorm:"created"` @@ -152,10 +153,14 @@ func TestDumpTables(t *testing.T) { _, err := testEngine.Insert([]TestDumpTableStruct{ {Name: "1", IsMan: true}, - {Name: "2\n"}, - {Name: "3;"}, - {Name: "4\n;\n''"}, - {Name: "5'\n"}, + {Name: "2\n", Data: []byte{'\000', '\001', '\002'}}, + {Name: "3;", Data: []byte("0x000102")}, + {Name: "4\n;\n''", Data: []byte("Help")}, + {Name: "5'\n", Data: []byte("0x48656c70")}, + {Name: "6\\n'\n", Data: []byte("48656c70")}, + {Name: "7\\n'\r\n", Data: []byte("7\\n'\r\n")}, + {Name: "x0809ee"}, + {Name: "090a10"}, }) assert.NoError(t, err) From 3d1c9fb761325f98ea2c5493bbb28e0431936784 Mon Sep 17 00:00:00 2001 From: jixianlqb Date: Sun, 16 Jan 2022 16:36:07 +0800 Subject: [PATCH 146/179] =?UTF-8?q?=E5=AF=B9=20DATE=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=BD=AC=E6=8D=A2=20(#2093)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对 DATE格式进行转换 Co-authored-by: laiqiangbin Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2093 Reviewed-by: Lunny Xiao Co-authored-by: jixianlqb Co-committed-by: jixianlqb --- convert/time.go | 10 ++++++++++ convert/time_test.go | 1 + 2 files changed, 11 insertions(+) diff --git a/convert/time.go b/convert/time.go index e53a19cd..cc2e0a10 100644 --- a/convert/time.go +++ b/convert/time.go @@ -48,6 +48,16 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t } dt = dt.In(convertedLocation) return &dt, nil + } else if len(s) == 10 && s[4] == '-' { + if s == "0000-00-00" || s == "0001-01-01" { + return &time.Time{}, nil + } + dt, err := time.ParseInLocation("2006-01-02", s, originalLocation) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil } else { i, err := strconv.ParseInt(s, 10, 64) if err == nil { diff --git a/convert/time_test.go b/convert/time_test.go index ef01b362..5ddceb64 100644 --- a/convert/time_test.go +++ b/convert/time_test.go @@ -16,6 +16,7 @@ func TestString2Time(t *testing.T) { assert.NoError(t, err) var kases = map[string]time.Time{ + "2021-08-10": time.Date(2021, 8, 10, 8, 0, 0, 0, expectedLoc), "2021-06-06T22:58:20+08:00": time.Date(2021, 6, 6, 22, 58, 20, 0, expectedLoc), "2021-07-11 10:44:00": time.Date(2021, 7, 11, 18, 44, 0, 0, expectedLoc), "2021-08-10T10:33:04Z": time.Date(2021, 8, 10, 18, 33, 04, 0, expectedLoc), From d13b607d75d50c494b1ffa24552f3733f2dde8af Mon Sep 17 00:00:00 2001 From: fuge Date: Sun, 16 Jan 2022 18:04:24 +0800 Subject: [PATCH 147/179] =?UTF-8?q?oracle=E5=88=86=E9=A1=B5=EF=BC=8Cstart?= =?UTF-8?q?=20=E4=B8=BA=200=20=E7=9A=84bug=20(#2098)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除前面判断即可。 Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2098 Reviewed-by: Lunny Xiao Co-authored-by: fuge Co-committed-by: fuge --- internal/statements/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/statements/query.go b/internal/statements/query.go index 16253417..8b383866 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -334,7 +334,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB fmt.Fprint(&buf, " LIMIT ", *pLimitN) } } else if dialect.URI().DBType == schemas.ORACLE { - if statement.Start != 0 && pLimitN != nil { + if pLimitN != nil { oldString := buf.String() buf.Reset() rawColStr := columnStr From e4e830bc78d321154b739d86b2ac05854ba2f6e5 Mon Sep 17 00:00:00 2001 From: appleboy Date: Sun, 16 Jan 2022 19:04:15 +0800 Subject: [PATCH 148/179] chore(lint): remove revive and misspell command from makefile (#2088) replace revive and misspell with golangci lint Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2088 Co-authored-by: appleboy Co-committed-by: appleboy --- .revive.toml | 29 ----------------------------- Makefile | 22 ---------------------- 2 files changed, 51 deletions(-) delete mode 100644 .revive.toml diff --git a/.revive.toml b/.revive.toml deleted file mode 100644 index 9e3b629d..00000000 --- a/.revive.toml +++ /dev/null @@ -1,29 +0,0 @@ -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -errorCode = 1 -warningCode = 1 - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.empty-lines] -[rule.errorf] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.indent-error-flow] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.struct-tag] -[rule.time-naming] -[rule.unexported-return] -[rule.unnecessary-stmt] -[rule.var-declaration] -[rule.var-naming] - arguments = [["ID", "UID", "UUID", "URL", "JSON"], []] \ No newline at end of file diff --git a/Makefile b/Makefile index 220c8592..b43c4a4c 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,6 @@ help: @echo " - clean delete integration files and build files but not css and js files" @echo " - fmt format the code" @echo " - lint run code linter" - @echo " - misspell check if a word is written wrong" @echo " - test run default unit test" @echo " - test-cockroach run integration tests for cockroach" @echo " - test-mysql run integration tests for mysql" @@ -131,27 +130,6 @@ golangci-lint-check: curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \ fi -.PHONY: revive -revive: - @hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/mgechev/revive; \ - fi - revive -config .revive.toml -exclude=./vendor/... ./... || exit 1 - -.PHONY: misspell -misspell: - @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/client9/misspell/cmd/misspell; \ - fi - misspell -w -i unknwon $(GOFILES) - -.PHONY: misspell-check -misspell-check: - @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/client9/misspell/cmd/misspell; \ - fi - misspell -error -i unknwon,destory $(GOFILES) - .PHONY: test test: go-check $(GO) test $(PACKAGES) From 7802393d01519f2e64537dfd1c8b9900ad4db9b5 Mon Sep 17 00:00:00 2001 From: finelog Date: Tue, 25 Jan 2022 11:09:41 +0800 Subject: [PATCH 149/179] fix reset colmap when counting distinct cols (#2096) when using distinct cols with FindAndCount, reset statement.ColumnMap will make the counting sql an syntax error, this pr try fix this. is this pr ok for merge? error sql logging: ```sql [SQL] SELECT DISTINCT `Fid` FROM `table_demo` WHERE Fkey = ? [val] [SQL] SELECT count(DISTINCT ) FROM `table_demo` WHERE Fkey = ? [val] ``` after fix: ```sql [SQL] SELECT DISTINCT `Fid` FROM `table_demo` WHERE Fkey = ? [val] [SQL] SELECT count(DISTINCT `Fid`) FROM `table_demo` WHERE Fkey = ? [val] ``` Co-authored-by: finelog Reviewed-on: https://gitea.com/xorm/xorm/pulls/2096 Reviewed-by: Lunny Xiao Co-authored-by: finelog Co-committed-by: finelog --- integrations/session_find_test.go | 30 ++++++++++++++++++++++++++++++ session_find.go | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index f7af45fa..0e0c7bb4 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -711,6 +711,36 @@ func TestFindAndCountWithGroupBy(t *testing.T) { assert.EqualValues(t, 2, len(results)) } +func TestFindAndCountWithDistinct(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type FindAndCountWithDistinct struct { + Id int64 + Age int `xorm:"index"` + Name string + } + + assert.NoError(t, testEngine.Sync(new(FindAndCountWithDistinct))) + + _, err := testEngine.Insert([]FindAndCountWithDistinct{ + { + Name: "test1", + Age: 10, + }, + { + Name: "test2", + Age: 20, + }, + }) + assert.NoError(t, err) + + var results []FindAndCountWithDistinct + cnt, err := testEngine.Distinct("`age`").FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, 2, len(results)) +} + type FindMapDevice struct { Deviceid string `xorm:"pk"` Status int diff --git a/session_find.go b/session_find.go index 57ac7bb7..caf79ee3 100644 --- a/session_find.go +++ b/session_find.go @@ -57,7 +57,7 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte if session.statement.SelectStr != "" { session.statement.SelectStr = "" } - if len(session.statement.ColumnMap) > 0 { + if len(session.statement.ColumnMap) > 0 && !session.statement.IsDistinct { session.statement.ColumnMap = []string{} } if session.statement.OrderStr != "" { From 3180c418c2454771181690a360a88204a2514826 Mon Sep 17 00:00:00 2001 From: fuge Date: Tue, 25 Jan 2022 13:28:46 +0800 Subject: [PATCH 150/179] bugfix :Oid It's a special index. You can't put it in (#2105) Co-authored-by: fuge <8342337@qq.com> Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2105 Co-authored-by: fuge Co-committed-by: fuge --- dialects/postgres.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dialects/postgres.go b/dialects/postgres.go index 76279d32..a5b080aa 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1300,6 +1300,19 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN indexType = schemas.IndexType } colNames = getIndexColName(indexdef) + + isSkip := false + //Oid It's a special index. You can't put it in + for _, element := range colNames { + if "oid" == element { + isSkip = true + break + } + } + if isSkip { + continue + } + var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { newIdxName := indexName[5+len(tableName):] From 79a21b68aafacfaa11b47f45ce595dc336c51036 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bonicoli Date: Thu, 31 Mar 2022 14:26:05 +0800 Subject: [PATCH 151/179] replace GitHub links: xorm has been moved to gitea.com (#2126) Co-authored-by: Pierre-Louis Bonicoli Reviewed-on: https://gitea.com/xorm/xorm/pulls/2126 Reviewed-by: Lunny Xiao Co-authored-by: Pierre-Louis Bonicoli Co-committed-by: Pierre-Louis Bonicoli --- CONTRIBUTING.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6925a5c..27e6929b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,13 @@ ## Contributing to xorm -`xorm` has a backlog of [pull requests](https://help.github.com/articles/using-pull-requests), but contributions are still very -much welcome. You can help with patch review, submitting bug reports, +`xorm` has a backlog of [pull requests](https://gitea.com/xorm/xorm/pulls), but contributions are still very +much welcome. You can help with patch review, submitting [bug reports](https://gitea.com/xorm/xorm/issues), or adding new functionality. There is no formal style guide, but please conform to the style of existing code and general Go formatting conventions when submitting patches. -* [fork a repo](https://help.github.com/articles/fork-a-repo) -* [creating a pull request ](https://help.github.com/articles/creating-a-pull-request) +* [fork the repo](https://gitea.com/repo/fork/2038) +* [creating a pull request ](https://docs.gitea.io/en-us/pull-request/) ### Language @@ -15,7 +15,7 @@ Since `xorm` is a world-wide open source project, please describe your issues or ### Sign your codes with comments ``` -// !! your comments +// !! your comments e.g., @@ -65,7 +65,7 @@ And if your branch is related with cache, you could also enable it via `TEST_CAC ### Patch review -Help review existing open [pull requests](https://help.github.com/articles/using-pull-requests) by commenting on the code or +Help review existing open [pull requests](https://gitea.com/xorm/xorm/pulls) by commenting on the code or proposed functionality. ### Bug reports From d195040cb941db63e82ba4f54a6c194bab0f614f Mon Sep 17 00:00:00 2001 From: finelog Date: Thu, 31 Mar 2022 17:20:29 +0800 Subject: [PATCH 152/179] fix session context overwrite when logSessionId not set (#2115) ref pr https://gitea.com/xorm/xorm/pulls/2053 i think the previous fix has some issue for example, i'm using session like this: ```go // logSessionID == false engine := NewEngine() // use ctx.SessionId to distinguish uniq request id cxt := context.WithValue(parent, log.SessionIDKey, "some unique request id") session := engine.NewSession().Context(ctx) ``` however, with pr 2053, `session.Context` can't get SessionId from ctx. this pr fix abrove issue, overwrite `session.Context()` only when `engine.logSessionID == true` please check it out,thanks! Co-authored-by: finelog Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2115 Co-authored-by: finelog Co-committed-by: finelog --- session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.go b/session.go index 21bbe6e1..3fc53e23 100644 --- a/session.go +++ b/session.go @@ -757,7 +757,7 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) { // Context sets the context on this session func (session *Session) Context(ctx context.Context) *Session { - if session.ctx != nil { + if session.engine.logSessionID && session.ctx != nil { ctx = context.WithValue(ctx, log.SessionIDKey, session.ctx.Value(log.SessionIDKey)) ctx = context.WithValue(ctx, log.SessionKey, session.ctx.Value(log.SessionKey)) ctx = context.WithValue(ctx, log.SessionShowSQLKey, session.ctx.Value(log.SessionShowSQLKey)) From b3f9c53d8abeb8870c579312d1a28293813e92fd Mon Sep 17 00:00:00 2001 From: getsu Date: Thu, 31 Mar 2022 23:57:40 +0800 Subject: [PATCH 153/179] =?UTF-8?q?oracle=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E6=8B=BC=E6=8E=A5AS=20(#2109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 #2108 Co-authored-by: chendy Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2109 Co-authored-by: getsu Co-committed-by: getsu --- dialects/table_name.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dialects/table_name.go b/dialects/table_name.go index 48b44de2..8a0baeac 100644 --- a/dialects/table_name.go +++ b/dialects/table_name.go @@ -11,6 +11,7 @@ import ( "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) // TableNameWithSchema will add schema prefix on table name if possible @@ -29,6 +30,9 @@ func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface switch tt := tableName.(type) { case []string: if len(tt) > 1 { + if dialect.URI().DBType == schemas.ORACLE { + return fmt.Sprintf("%v %v", quote(tt[0]), quote(tt[1])) + } return fmt.Sprintf("%v AS %v", quote(tt[0]), quote(tt[1])) } else if len(tt) == 1 { return quote(tt[0]) @@ -54,6 +58,9 @@ func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface } } if l > 1 { + if dialect.URI().DBType == schemas.ORACLE { + return fmt.Sprintf("%v %v", quote(table), quote(fmt.Sprintf("%v", tt[1]))) + } return fmt.Sprintf("%v AS %v", quote(table), quote(fmt.Sprintf("%v", tt[1]))) } else if l == 1 { return quote(table) From e858b75756dc6b0340d9096e91f1007cba2f8b3c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 14 Apr 2022 10:12:39 +0800 Subject: [PATCH 154/179] Update changelog for 1.3.0 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0f93e7..fed4e261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.3.0](https://gitea.com/xorm/xorm/releases/tag/1.3.0) - 2022-04-14 + +* BREAKING + * New Prepare useage (#2061) + * Make Get and Rows.Scan accept multiple parameters (#2029) + * Drop sync function and rename sync2 to sync (#2018) +* FEATURES + * Add dameng support (#2007) +* BUGFIXES + * bugfix :Oid It's a special index. You can't put it in (#2105) + * Fix new-lined query execution in master DB node. (#2066) + * Fix bug of Rows (#2048) + * Fix bug (#2046) + * fix panic when `Iterate()` fails (#2040) + * fix panic when convert sql and args with nil time.Time pointer (#2038) +* ENHANCEMENTS + * Fix to add session.statement.IsForUpdate check in Session.queryRows() (#2064) + * Expose ScanString / ScanInterface and etc (#2039) +* TESTING + * Add test for mysql tls (#2049) +* BUILD + * Upgrade dependencies modules (#2078) +* MISC + * Fix oracle keyword AS (#2109) + * Some performance optimization for get (#2043) + ## [1.2.2](https://gitea.com/xorm/xorm/releases/tag/1.2.2) - 2021-08-11 * MISC From ea9bba0d145211974a5bbe9d6b79324c257b6fc4 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bonicoli Date: Sun, 17 Apr 2022 18:03:29 +0800 Subject: [PATCH 155/179] PostgreSQL: enable comment on column (#2131) The [oldest unsupported version documentation](https://www.postgresql.org/docs/7.1/sql-comment.html) states that comment on a column is supported. Update `TestGetColumnsComment` in order to check both MySQL/MariaDB and PostgreSQL. Co-authored-by: Pierre-Louis Bonicoli Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2131 Reviewed-by: Lunny Xiao Co-authored-by: Pierre-Louis Bonicoli Co-committed-by: Pierre-Louis Bonicoli --- dialects/postgres.go | 8 ++++++++ integrations/engine_test.go | 18 ++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index a5b080aa..83e4187f 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1354,6 +1354,14 @@ func (db *postgres) CreateTableSQL(ctx context.Context, queryer core.Queryer, ta commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'", quoter.Quote(tableName), table.Comment) } + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + + if len(col.Comment) > 0 { + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + } + } + return createTableSQL + commentSQL, true, nil } diff --git a/integrations/engine_test.go b/integrations/engine_test.go index cdcdd6be..997c8962 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -255,33 +255,31 @@ func TestDBVersion(t *testing.T) { fmt.Println(testEngine.Dialect().URI().DBType, "version is", version) } -func TestGetColumns(t *testing.T) { - if testEngine.Dialect().URI().DBType != schemas.POSTGRES { +func TestGetColumnsComment(t *testing.T) { + switch testEngine.Dialect().URI().DBType { + case schemas.POSTGRES, schemas.MYSQL: + default: t.Skip() return } + comment := "this is a comment" type TestCommentStruct struct { - HasComment int + HasComment int `xorm:"comment('this is a comment')"` NoComment int } assertSync(t, new(TestCommentStruct)) - comment := "this is a comment" - sql := fmt.Sprintf("comment on column %s.%s is '%s'", testEngine.TableName(new(TestCommentStruct), true), "has_comment", comment) - _, err := testEngine.Exec(sql) - assert.NoError(t, err) - tables, err := testEngine.DBMetas() assert.NoError(t, err) tableName := testEngine.GetColumnMapper().Obj2Table("TestCommentStruct") var hasComment, noComment string for _, table := range tables { if table.Name == tableName { - col := table.GetColumn("has_comment") + col := table.GetColumn(testEngine.GetColumnMapper().Obj2Table("HasComment")) assert.NotNil(t, col) hasComment = col.Comment - col2 := table.GetColumn("no_comment") + col2 := table.GetColumn(testEngine.GetColumnMapper().Obj2Table("NoComment")) assert.NotNil(t, col2) noComment = col2.Comment break From 8f2596bf64d4a91c0996c29e5266e01cbb2fce84 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 22 Apr 2022 10:16:35 +0800 Subject: [PATCH 156/179] some improvement (#2136) Fix #2134 and replace #2135 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2136 --- integrations/rows_test.go | 76 +++++++++++++++++++++++++++- integrations/session_find_test.go | 66 ++++++++++++++++++++---- integrations/session_get_test.go | 83 ++++++++++++++++++++++--------- scan.go | 24 ++++----- session.go | 56 ++++++++++++++------- 5 files changed, 238 insertions(+), 67 deletions(-) diff --git a/integrations/rows_test.go b/integrations/rows_test.go index 10f11453..e354b75e 100644 --- a/integrations/rows_test.go +++ b/integrations/rows_test.go @@ -70,7 +70,7 @@ func TestRows(t *testing.T) { } assert.EqualValues(t, 1, cnt) - var tbName = testEngine.Quote(testEngine.TableName(user, true)) + tbName := testEngine.Quote(testEngine.TableName(user, true)) rows2, err := testEngine.SQL("SELECT * FROM " + tbName).Rows(new(UserRows)) assert.NoError(t, err) defer rows2.Close() @@ -92,7 +92,7 @@ func TestRowsMyTableName(t *testing.T) { IsMan bool } - var tableName = "user_rows_my_table_name" + tableName := "user_rows_my_table_name" assert.NoError(t, testEngine.Table(tableName).Sync(new(UserRowsMyTable))) @@ -206,3 +206,75 @@ func TestRowsScanVars(t *testing.T) { assert.NoError(t, rows.Err()) assert.EqualValues(t, 2, cnt) } + +func TestRowsScanBytes(t *testing.T) { + type RowsScanBytes struct { + Id int64 + Bytes1 []byte + Bytes2 []byte + } + + assert.NoError(t, PrepareEngine()) + assert.NoError(t, testEngine.Sync(new(RowsScanBytes))) + + cnt, err := testEngine.Insert(&RowsScanBytes{ + Bytes1: []byte("bytes1"), + Bytes2: []byte("bytes2"), + }, &RowsScanBytes{ + Bytes1: []byte("bytes1-1"), + Bytes2: []byte("bytes2-2"), + }) + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + + { + rows, err := testEngine.Cols("bytes1, bytes2").Rows(new(RowsScanBytes)) + assert.NoError(t, err) + defer rows.Close() + + cnt = 0 + var bytes1 []byte + var bytes2 []byte + for rows.Next() { + err = rows.Scan(&bytes1, &bytes2) + assert.NoError(t, err) + if cnt == 0 { + assert.EqualValues(t, []byte("bytes1"), bytes1) + assert.EqualValues(t, []byte("bytes2"), bytes2) + } else if cnt == 1 { + // bytes1 now should be `bytes1` but will be override + assert.EqualValues(t, []byte("bytes1-1"), bytes1) + assert.EqualValues(t, []byte("bytes2-2"), bytes2) + } + cnt++ + } + assert.NoError(t, rows.Err()) + assert.EqualValues(t, 2, cnt) + rows.Close() + } + + { + rows, err := testEngine.Cols("bytes1, bytes2").Rows(new(RowsScanBytes)) + assert.NoError(t, err) + defer rows.Close() + + cnt = 0 + var rsb RowsScanBytes + for rows.Next() { + err = rows.Scan(&rsb) + assert.NoError(t, err) + if cnt == 0 { + assert.EqualValues(t, []byte("bytes1"), rsb.Bytes1) + assert.EqualValues(t, []byte("bytes2"), rsb.Bytes2) + } else if cnt == 1 { + // bytes1 now should be `bytes1` but will be override + assert.EqualValues(t, []byte("bytes1-1"), rsb.Bytes1) + assert.EqualValues(t, []byte("bytes2-2"), rsb.Bytes2) + } + cnt++ + } + assert.NoError(t, rows.Err()) + assert.EqualValues(t, 2, cnt) + rows.Close() + } +} diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 0e0c7bb4..ae8779ff 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -40,14 +40,14 @@ func TestJoinLimit(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - var checklist = CheckList{ + checklist := CheckList{ Eid: emp.Id, } cnt, err = testEngine.Insert(&checklist) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - var salary = Salary{ + salary := Salary{ Lid: checklist.Id, } cnt, err = testEngine.Insert(&salary) @@ -89,7 +89,7 @@ func TestFind(t *testing.T) { assert.NoError(t, err) users2 := make([]Userinfo, 0) - var tbName = testEngine.Quote(testEngine.TableName(new(Userinfo), true)) + tbName := testEngine.Quote(testEngine.TableName(new(Userinfo), true)) err = testEngine.SQL("select * from " + tbName).Find(&users2) assert.NoError(t, err) } @@ -119,7 +119,7 @@ func (TeamUser) TableName() string { } func TestFind3(t *testing.T) { - var teamUser = new(TeamUser) + teamUser := new(TeamUser) assert.NoError(t, PrepareEngine()) err := testEngine.Sync(new(Team), teamUser) assert.NoError(t, err) @@ -426,7 +426,7 @@ func TestFindBool(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, cnt) - var results = make([]FindBoolStruct, 0, 2) + results := make([]FindBoolStruct, 0, 2) err = testEngine.Find(&results) assert.NoError(t, err) assert.EqualValues(t, 2, len(results)) @@ -457,7 +457,7 @@ func TestFindMark(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, cnt) - var results = make([]Mark, 0, 2) + results := make([]Mark, 0, 2) err = testEngine.Find(&results) assert.NoError(t, err) assert.EqualValues(t, 2, len(results)) @@ -486,7 +486,7 @@ func TestFindAndCountOneFunc(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, cnt) - var results = make([]FindAndCountStruct, 0, 2) + results := make([]FindAndCountStruct, 0, 2) cnt, err = testEngine.Limit(1).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) @@ -611,14 +611,14 @@ func TestFindAndCount2(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(TestFindAndCountUser), new(TestFindAndCountHotel)) - var u = TestFindAndCountUser{ + u := TestFindAndCountUser{ Name: "myname", } cnt, err := testEngine.Insert(&u) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - var hotel = TestFindAndCountHotel{ + hotel := TestFindAndCountHotel{ Name: "myhotel", Code: "111", Region: "222", @@ -1063,7 +1063,7 @@ func TestUpdateFind(t *testing.T) { session := testEngine.NewSession() defer session.Close() - var tuf = TestUpdateFind{ + tuf := TestUpdateFind{ Name: "test", } _, err := session.Insert(&tuf) @@ -1095,7 +1095,7 @@ func TestFindAnonymousStruct(t *testing.T) { assert.EqualValues(t, 1, cnt) assert.NoError(t, err) - var findRes = make([]struct { + findRes := make([]struct { Id int64 Name string }, 0) @@ -1115,3 +1115,47 @@ func TestFindAnonymousStruct(t *testing.T) { assert.EqualValues(t, 1, findRes[0].Id) assert.EqualValues(t, "xlw", findRes[0].Name) } + +func TestFindBytesVars(t *testing.T) { + type FindBytesVars struct { + Id int64 + Bytes1 []byte + Bytes2 []byte + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(FindBytesVars)) + + _, err := testEngine.Insert([]FindBytesVars{ + { + Bytes1: []byte("bytes1"), + Bytes2: []byte("bytes2"), + }, + { + Bytes1: []byte("bytes1-1"), + Bytes2: []byte("bytes2-2"), + }, + }) + assert.NoError(t, err) + + var gbv []FindBytesVars + err = testEngine.Find(&gbv) + assert.NoError(t, err) + assert.EqualValues(t, 2, len(gbv)) + assert.EqualValues(t, []byte("bytes1"), gbv[0].Bytes1) + assert.EqualValues(t, []byte("bytes2"), gbv[0].Bytes2) + assert.EqualValues(t, []byte("bytes1-1"), gbv[1].Bytes1) + assert.EqualValues(t, []byte("bytes2-2"), gbv[1].Bytes2) + + err = testEngine.Find(&gbv) + assert.NoError(t, err) + assert.EqualValues(t, 4, len(gbv)) + assert.EqualValues(t, []byte("bytes1"), gbv[0].Bytes1) + assert.EqualValues(t, []byte("bytes2"), gbv[0].Bytes2) + assert.EqualValues(t, []byte("bytes1-1"), gbv[1].Bytes1) + assert.EqualValues(t, []byte("bytes2-2"), gbv[1].Bytes2) + assert.EqualValues(t, []byte("bytes1"), gbv[2].Bytes1) + assert.EqualValues(t, []byte("bytes2"), gbv[2].Bytes2) + assert.EqualValues(t, []byte("bytes1-1"), gbv[3].Bytes1) + assert.EqualValues(t, []byte("bytes2-2"), gbv[3].Bytes2) +} diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 5d1558f4..841ec709 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -35,7 +35,7 @@ func TestGetVar(t *testing.T) { assert.NoError(t, testEngine.Sync(new(GetVar))) - var data = GetVar{ + data := GetVar{ Msg: "hi", Age: 28, Money: 1.5, @@ -175,7 +175,7 @@ func TestGetVar(t *testing.T) { assert.NoError(t, err) assert.Equal(t, false, has) - var valuesString = make(map[string]string) + valuesString := make(map[string]string) has, err = testEngine.Table("get_var").Get(&valuesString) assert.NoError(t, err) assert.Equal(t, true, has) @@ -187,7 +187,7 @@ func TestGetVar(t *testing.T) { // for mymysql driver, interface{} will be []byte, so ignore it currently if testEngine.DriverName() != "mymysql" { - var valuesInter = make(map[string]interface{}) + valuesInter := make(map[string]interface{}) has, err = testEngine.Table("get_var").Where("`id` = ?", 1).Select("*").Get(&valuesInter) assert.NoError(t, err) assert.Equal(t, true, has) @@ -198,7 +198,7 @@ func TestGetVar(t *testing.T) { assert.Equal(t, "1.5", fmt.Sprintf("%v", valuesInter["money"])) } - var valuesSliceString = make([]string, 5) + valuesSliceString := make([]string, 5) has, err = testEngine.Table("get_var").Get(&valuesSliceString) assert.NoError(t, err) assert.Equal(t, true, has) @@ -207,7 +207,7 @@ func TestGetVar(t *testing.T) { assert.Equal(t, "28", valuesSliceString[2]) assert.Equal(t, "1.5", valuesSliceString[3]) - var valuesSliceInter = make([]interface{}, 5) + valuesSliceInter := make([]interface{}, 5) has, err = testEngine.Table("get_var").Get(&valuesSliceInter) assert.NoError(t, err) assert.Equal(t, true, has) @@ -317,7 +317,7 @@ func TestGetMap(t *testing.T) { _, err := testEngine.Exec(fmt.Sprintf("INSERT INTO %s (`is_man`) VALUES (NULL)", tableName)) assert.NoError(t, err) - var valuesString = make(map[string]string) + valuesString := make(map[string]string) has, err := testEngine.Table("userinfo_map").Get(&valuesString) assert.NoError(t, err) assert.Equal(t, true, has) @@ -336,7 +336,7 @@ func TestGetError(t *testing.T) { assertSync(t, new(GetError)) - var info = new(GetError) + info := new(GetError) has, err := testEngine.Get(&info) assert.False(t, has) assert.Error(t, err) @@ -456,7 +456,7 @@ func TestGetActionMapping(t *testing.T) { }) assert.NoError(t, err) - var valuesSlice = make([]string, 2) + valuesSlice := make([]string, 2) has, err := testEngine.Table(new(ActionMapping)). Cols("script_id", "rollback_id"). ID("1").Get(&valuesSlice) @@ -483,7 +483,7 @@ func TestGetStructId(t *testing.T) { Id int64 } - //var id int64 + // var id int64 var maxid maxidst sql := "select max(`id`) as id from " + testEngine.Quote(testEngine.TableName(&TestGetStruct{}, true)) has, err := testEngine.SQL(sql).Get(&maxid) @@ -693,7 +693,7 @@ func TestCustomTypes(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(TestCustomizeStruct)) - var s = TestCustomizeStruct{ + s := TestCustomizeStruct{ Name: "test", Age: 32, } @@ -763,7 +763,7 @@ func TestGetBigFloat(t *testing.T) { assertSync(t, new(GetBigFloat)) { - var gf = GetBigFloat{ + gf := GetBigFloat{ Money: big.NewFloat(999999.99), } _, err := testEngine.Insert(&gf) @@ -774,8 +774,8 @@ func TestGetBigFloat(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) - //fmt.Println(m.Cmp(gf.Money)) - //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + // fmt.Println(m.Cmp(gf.Money)) + // assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) } type GetBigFloat2 struct { @@ -788,7 +788,7 @@ func TestGetBigFloat(t *testing.T) { assertSync(t, new(GetBigFloat2)) { - var gf2 = GetBigFloat2{ + gf2 := GetBigFloat2{ Money: big.NewFloat(9999999.99), Money2: *big.NewFloat(99.99), } @@ -800,8 +800,8 @@ func TestGetBigFloat(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.True(t, m2.String() == gf2.Money.String(), "%v != %v", m2.String(), gf2.Money.String()) - //fmt.Println(m.Cmp(gf.Money)) - //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + // fmt.Println(m.Cmp(gf.Money)) + // assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) var gf3 GetBigFloat2 has, err = testEngine.ID(gf2.Id).Get(&gf3) @@ -829,7 +829,7 @@ func TestGetDecimal(t *testing.T) { assertSync(t, new(GetDecimal)) { - var gf = GetDecimal{ + gf := GetDecimal{ Money: decimal.NewFromFloat(999999.99), } _, err := testEngine.Insert(&gf) @@ -840,8 +840,8 @@ func TestGetDecimal(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) - //fmt.Println(m.Cmp(gf.Money)) - //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + // fmt.Println(m.Cmp(gf.Money)) + // assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) } type GetDecimal2 struct { @@ -854,7 +854,7 @@ func TestGetDecimal(t *testing.T) { { v := decimal.NewFromFloat(999999.99) - var gf = GetDecimal2{ + gf := GetDecimal2{ Money: &v, } _, err := testEngine.Insert(&gf) @@ -865,10 +865,11 @@ func TestGetDecimal(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.True(t, m.String() == gf.Money.String(), "%v != %v", m.String(), gf.Money.String()) - //fmt.Println(m.Cmp(gf.Money)) - //assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) + // fmt.Println(m.Cmp(gf.Money)) + // assert.True(t, m.Cmp(gf.Money) == 0, "%v != %v", m.String(), gf.Money.String()) } } + func TestGetTime(t *testing.T) { type GetTimeStruct struct { Id int64 @@ -878,7 +879,7 @@ func TestGetTime(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(GetTimeStruct)) - var gts = GetTimeStruct{ + gts := GetTimeStruct{ CreateTime: time.Now().In(testEngine.GetTZLocation()), } _, err := testEngine.Insert(>s) @@ -976,3 +977,39 @@ func TestGetWithPrepare(t *testing.T) { err = sess.Commit() assert.NoError(t, err) } + +func TestGetBytesVars(t *testing.T) { + type GetBytesVars struct { + Id int64 + Bytes1 []byte + Bytes2 []byte + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetBytesVars)) + + _, err := testEngine.Insert([]GetBytesVars{ + { + Bytes1: []byte("bytes1"), + Bytes2: []byte("bytes2"), + }, + { + Bytes1: []byte("bytes1-1"), + Bytes2: []byte("bytes2-2"), + }, + }) + assert.NoError(t, err) + + var gbv GetBytesVars + has, err := testEngine.Asc("id").Get(&gbv) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, []byte("bytes1"), gbv.Bytes1) + assert.EqualValues(t, []byte("bytes2"), gbv.Bytes2) + + has, err = testEngine.Desc("id").NoAutoCondition().Get(&gbv) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, []byte("bytes1-1"), gbv.Bytes1) + assert.EqualValues(t, []byte("bytes2-2"), gbv.Bytes2) +} diff --git a/scan.go b/scan.go index 10988bdb..00cee4d7 100644 --- a/scan.go +++ b/scan.go @@ -22,7 +22,7 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { switch t := bean.(type) { case *interface{}: return t, false, nil - case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes: + case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes, *[]byte: return t, false, nil case *time.Time: return &sql.NullString{}, true, nil @@ -67,7 +67,7 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) { case reflect.Uint32, reflect.Uint, reflect.Uint16, reflect.Uint8: return &convert.NullUint32{}, true, nil default: - return nil, false, fmt.Errorf("unsupported type: %#v", bean) + return nil, false, fmt.Errorf("genScanResultsByBeanNullable: unsupported type: %#v", bean) } } @@ -125,12 +125,12 @@ func genScanResultsByBean(bean interface{}) (interface{}, bool, error) { case reflect.Float64: return new(float64), true, nil default: - return nil, false, fmt.Errorf("unsupported type: %#v", bean) + return nil, false, fmt.Errorf("genScanResultsByBean: unsupported type: %#v", bean) } } func (engine *Engine) scanStringInterface(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) { - var scanResults = make([]interface{}, len(types)) + scanResults := make([]interface{}, len(types)) for i := 0; i < len(types); i++ { var s sql.NullString scanResults[i] = &s @@ -144,8 +144,8 @@ func (engine *Engine) scanStringInterface(rows *core.Rows, fields []string, type // scan is a wrap of driver.Scan but will automatically change the input values according requirements func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.ColumnType, vv ...interface{}) error { - var scanResults = make([]interface{}, 0, len(types)) - var replaces = make([]bool, 0, len(types)) + scanResults := make([]interface{}, 0, len(types)) + replaces := make([]bool, 0, len(types)) var err error for _, v := range vv { var replaced bool @@ -194,7 +194,7 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column } func (engine *Engine) scanInterfaces(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) { - var scanResultContainers = make([]interface{}, len(types)) + scanResultContainers := make([]interface{}, len(types)) for i := 0; i < len(types); i++ { scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName()) if err != nil { @@ -212,8 +212,8 @@ func (engine *Engine) scanInterfaces(rows *core.Rows, fields []string, types []* // row -> map[string]interface{} func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]interface{}, error) { - var resultsMap = make(map[string]interface{}, len(fields)) - var scanResultContainers = make([]interface{}, len(fields)) + resultsMap := make(map[string]interface{}, len(fields)) + scanResultContainers := make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName()) if err != nil { @@ -277,7 +277,7 @@ func (engine *Engine) ScanInterfaceMaps(rows *core.Rows) (resultsSlice []map[str // row -> map[string]string func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) { - var scanResults = make([]interface{}, len(fields)) + scanResults := make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { var s sql.NullString scanResults[i] = &s @@ -353,7 +353,7 @@ func (engine *Engine) ScanStringMaps(rows *core.Rows) (resultsSlice []map[string // row -> map[string][]byte func convertMapStr2Bytes(m map[string]string) map[string][]byte { - var r = make(map[string][]byte, len(m)) + r := make(map[string][]byte, len(m)) for k, v := range m { r[k] = []byte(v) } @@ -392,7 +392,7 @@ func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fie return nil, err } - var results = make([]string, 0, len(fields)) + results := make([]string, 0, len(fields)) for i := 0; i < len(fields); i++ { results = append(results, scanResults[i].(*sql.NullString).String) } diff --git a/session.go b/session.go index 3fc53e23..64b98bfe 100644 --- a/session.go +++ b/session.go @@ -79,7 +79,7 @@ type Session struct { afterClosures []func(interface{}) afterProcessors []executedProcessor - stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) + stmtCache map[uint32]*core.Stmt // key: hash.Hash32 of (queryStr, len(queryStr)) txStmtCache map[uint32]*core.Stmt // for tx statement lastSQL string @@ -314,7 +314,7 @@ func (session *Session) Cascade(trueOrFalse ...bool) *Session { // MustLogSQL means record SQL or not and don't follow engine's setting func (session *Session) MustLogSQL(logs ...bool) *Session { - var showSQL = true + showSQL := true if len(logs) > 0 { showSQL = logs[0] } @@ -396,7 +396,7 @@ func (session *Session) doPrepareTx(sqlStr string) (stmt *core.Stmt, err error) } func getField(dataStruct *reflect.Value, table *schemas.Table, colName string, idx int) (*schemas.Column, *reflect.Value, error) { - var col = table.GetColumnIdx(colName, idx) + col := table.GetColumnIdx(colName, idx) if col == nil { return nil, nil, ErrFieldIsNotExist{colName, table.Name} } @@ -420,9 +420,10 @@ type Cell *interface{} func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sql.ColumnType, table *schemas.Table, newElemFunc func([]string) reflect.Value, - sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { + sliceValueSetFunc func(*reflect.Value, schemas.PK) error, +) error { for rows.Next() { - var newValue = newElemFunc(fields) + newValue := newElemFunc(fields) bean := newValue.Interface() dataStruct := newValue.Elem() @@ -533,8 +534,11 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) } +var uint8ZeroValue = reflect.ValueOf(uint8(0)) + func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflect.Value, - scanResult interface{}, table *schemas.Table) error { + scanResult interface{}, table *schemas.Table, +) error { v, ok := scanResult.(*interface{}) if ok { scanResult = *v @@ -596,7 +600,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec return nil case reflect.Complex64, reflect.Complex128: return setJSON(fieldValue, fieldType, scanResult) - case reflect.Slice, reflect.Array: + case reflect.Slice: bs, ok := convert.AsBytes(scanResult) if ok && fieldType.Elem().Kind() == reflect.Uint8 { if col.SQLType.IsText() { @@ -607,15 +611,29 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec } fieldValue.Set(x.Elem()) } else { - if fieldValue.Len() > 0 { - for i := 0; i < fieldValue.Len(); i++ { - if i < vv.Len() { - fieldValue.Index(i).Set(vv.Index(i)) - } - } - } else { - for i := 0; i < vv.Len(); i++ { - fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) + fieldValue.Set(reflect.ValueOf(bs)) + } + return nil + } + case reflect.Array: + bs, ok := convert.AsBytes(scanResult) + if ok && fieldType.Elem().Kind() == reflect.Uint8 { + if col.SQLType.IsText() { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } else { + if fieldValue.Len() < vv.Len() { + return fmt.Errorf("Set field %s[Array] failed because of data too long", col.Name) + } + for i := 0; i < fieldValue.Len(); i++ { + if i < vv.Len() { + fieldValue.Index(i).Set(vv.Index(i)) + } else { + fieldValue.Index(i).Set(uint8ZeroValue) } } } @@ -659,7 +677,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec if len(table.PrimaryKeys) != 1 { return errors.New("unsupported non or composited primary key cascade") } - var pk = make(schemas.PK, len(table.PrimaryKeys)) + pk := make(schemas.PK, len(table.PrimaryKeys)) pk[0], err = asKind(vv, reflect.TypeOf(scanResult)) if err != nil { return err @@ -694,11 +712,11 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b buildAfterProcessors(session, bean) - var tempMap = make(map[string]int) + tempMap := make(map[string]int) var pk schemas.PK for i, colName := range fields { var idx int - var lKey = strings.ToLower(colName) + lKey := strings.ToLower(colName) var ok bool if idx, ok = tempMap[lKey]; !ok { From e1d43656672824d61332eeec7d529402dcc5e0bf Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bonicoli Date: Fri, 22 Apr 2022 10:48:53 +0800 Subject: [PATCH 157/179] MySQL/MariaDB: return max length for text columns (#2133) MySQL/MariaDB: return max length for text columns using `CHARACTER_MAXIMUM_LENGTH`. Tests: * add an integration test: `TestGetColumnsLength` * update `TestSyncTable3` since `TableInfo` isn't able to provide the column size Co-authored-by: Pierre-Louis Bonicoli Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2133 Reviewed-by: Lunny Xiao Co-authored-by: Pierre-Louis Bonicoli Co-committed-by: Pierre-Louis Bonicoli --- dialects/mysql.go | 14 +++++++++--- integrations/engine_test.go | 33 +++++++++++++++++++++++++++++ integrations/session_schema_test.go | 5 ++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/dialects/mysql.go b/dialects/mysql.go index 1fad3fee..56ba66c7 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -399,7 +399,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName "(SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) = 2 && " + "SUBSTRING_INDEX(SUBSTRING(VERSION(), 6), '-', 1) >= 7)))))" s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + - " `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, " + + " `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, `CHARACTER_MAXIMUM_LENGTH`, " + alreadyQuoted + " AS NEEDS_QUOTE " + "FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + " ORDER BY `COLUMNS`.ORDINAL_POSITION" @@ -418,8 +418,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName var columnName, nullableStr, colType, colKey, extra, comment string var alreadyQuoted, isUnsigned bool - var colDefault *string - err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &alreadyQuoted) + var colDefault, maxLength *string + err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &maxLength, &alreadyQuoted) if err != nil { return nil, nil, err } @@ -478,6 +478,14 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } } } + } else { + switch colType { + case "MEDIUMTEXT", "LONGTEXT", "TEXT": + len1, err = strconv.Atoi(*maxLength) + if err != nil { + return nil, nil, err + } + } } if isUnsigned { colType = "UNSIGNED " + colType diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 997c8962..905c4f24 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -288,3 +288,36 @@ func TestGetColumnsComment(t *testing.T) { assert.Equal(t, comment, hasComment) assert.Zero(t, noComment) } + +func TestGetColumnsLength(t *testing.T) { + var max_length int + switch testEngine.Dialect().URI().DBType { + case + schemas.POSTGRES: + max_length = 0 + case + schemas.MYSQL: + max_length = 65535 + default: + t.Skip() + return + } + + type TestLengthStringStruct struct { + Content string `xorm:"TEXT NOT NULL"` + } + + assertSync(t, new(TestLengthStringStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + tableLengthStringName := testEngine.GetColumnMapper().Obj2Table("TestLengthStringStruct") + for _, table := range tables { + if table.Name == tableLengthStringName { + col := table.GetColumn("content") + assert.Equal(t, col.Length, max_length) + assert.Zero(t, col.Length2) + break + } + } +} diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 7dc0af76..3212d027 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -6,6 +6,7 @@ package integrations import ( "fmt" + "strings" "testing" "time" @@ -248,7 +249,9 @@ func TestSyncTable3(t *testing.T) { tableInfo, err := testEngine.TableInfo(new(SyncTable5)) assert.NoError(t, err) assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("name")), testEngine.Dialect().SQLType(tables[0].GetColumn("name"))) - assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("text")), testEngine.Dialect().SQLType(tables[0].GetColumn("text"))) + /* Engine.DBMetas() returns the size of the column from the database but Engine.TableInfo() might not be able to guess the column size. + For example using MySQL/MariaDB: when utf-8 charset is used, "`xorm:"TEXT(21846)`" creates a MEDIUMTEXT column not a TEXT column. */ + assert.True(t, testEngine.Dialect().SQLType(tables[0].GetColumn("text")) == testEngine.Dialect().SQLType(tableInfo.GetColumn("text")) || strings.HasPrefix(testEngine.Dialect().SQLType(tables[0].GetColumn("text")), testEngine.Dialect().SQLType(tableInfo.GetColumn("text"))+"(")) assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("char")), testEngine.Dialect().SQLType(tables[0].GetColumn("char"))) assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_char"))) assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_var_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_var_char"))) From 2c064b6da69c93795c29074590f5f7c6d5820b4a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 22 Apr 2022 14:56:26 +0800 Subject: [PATCH 158/179] Add test for find date (#2121) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2121 --- integrations/session_find_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index ae8779ff..7f42d096 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -1159,3 +1159,31 @@ func TestFindBytesVars(t *testing.T) { assert.EqualValues(t, []byte("bytes1-1"), gbv[3].Bytes1) assert.EqualValues(t, []byte("bytes2-2"), gbv[3].Bytes2) } + +func TestUpdateFindDate(t *testing.T) { + type TestUpdateFindDate struct { + Id int64 + Name string + Tm time.Time `xorm:"DATE created"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(TestUpdateFindDate)) + + session := testEngine.NewSession() + defer session.Close() + + tuf := TestUpdateFindDate{ + Name: "test", + } + _, err := session.Insert(&tuf) + assert.NoError(t, err) + _, err = session.Where("`id` = ?", tuf.Id).Update(&TestUpdateFindDate{}) + assert.EqualError(t, xorm.ErrNoColumnsTobeUpdated, err.Error()) + + var tufs []TestUpdateFindDate + err = session.Find(&tufs) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tufs)) + assert.EqualValues(t, tuf.Tm.Format("2006-01-02"), tufs[0].Tm.Format("2006-01-02")) +} From f7e9fb74acc97a9e4fedc7a97f444de5f3f235c4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 23 Apr 2022 17:19:37 +0800 Subject: [PATCH 159/179] return a clear error for set TEXT type as compare condition (#2062) Fix #523 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2062 --- integrations/types_test.go | 23 ++++++++++++++++------- internal/statements/statement.go | 32 +++++++++++++++++++------------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/integrations/types_test.go b/integrations/types_test.go index d166845e..1c815b7a 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -30,7 +30,7 @@ func TestArrayField(t *testing.T) { assert.NoError(t, testEngine.Sync(new(ArrayStruct))) - var as = ArrayStruct{ + as := ArrayStruct{ Name: [20]byte{ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, @@ -54,7 +54,7 @@ func TestArrayField(t *testing.T) { assert.EqualValues(t, 1, len(arrs)) assert.Equal(t, as.Name, arrs[0].Name) - var newName = [20]byte{ + newName := [20]byte{ 90, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, @@ -252,9 +252,11 @@ func TestConversion(t *testing.T) { assert.Nil(t, c1.Nullable2) } -type MyInt int -type MyUInt uint -type MyFloat float64 +type ( + MyInt int + MyUInt uint + MyFloat float64 +) type MyStruct struct { Type MyInt @@ -273,7 +275,7 @@ type MyStruct struct { UIA32 []uint32 UIA64 []uint64 UI uint - //C64 complex64 + // C64 complex64 MSS map[string]string } @@ -304,6 +306,13 @@ func TestCustomType1(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + // since mssql don't support use text as index condition, we have to ignore below + // get and find tests + if testEngine.Dialect().URI().DBType == schemas.MSSQL { + t.Skip() + return + } + fmt.Println(i) i.NameArray = []string{} i.MSS = map[string]string{} @@ -598,7 +607,7 @@ func TestMyArray(t *testing.T) { assert.NoError(t, PrepareEngine()) assertSync(t, new(MyArrayStruct)) - var v = [20]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + v := [20]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} _, err := testEngine.Insert(&MyArrayStruct{ Content: v, }) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 2a7ae8b0..3069561e 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -304,7 +304,7 @@ func (statement *Statement) needTableName() bool { func (statement *Statement) colName(col *schemas.Column, tableName string) string { if statement.needTableName() { - var nm = tableName + nm := tableName if len(statement.TableAlias) > 0 { nm = statement.TableAlias } @@ -765,7 +765,7 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect if len(table.PrimaryKeys) == 1 { pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) // fix non-int pk issues - //if pkField.Int() != 0 { + // if pkField.Int() != 0 { if pkField.IsValid() && !utils.IsZero(pkField.Interface()) { return pkField.Interface(), true, nil } @@ -814,7 +814,8 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool, - mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) { + mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool, +) (builder.Cond, error) { var conds []builder.Cond for _, col := range table.Columns() { if !includeVersion && col.IsVersion { @@ -827,17 +828,13 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, continue } - if statement.dialect.URI().DBType == schemas.MSSQL && (col.SQLType.Name == schemas.Text || - col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { - continue - } if col.IsJSON { continue } var colName string if addedTableName { - var nm = tableName + nm := tableName if len(aliasName) > 0 { nm = aliasName } @@ -862,6 +859,15 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, continue } + if statement.dialect.URI().DBType == schemas.MSSQL && (col.SQLType.Name == schemas.Text || + col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { + if utils.IsValueZero(fieldValue) { + continue + } + + return nil, fmt.Errorf("column %s is a TEXT type with data %#v which cannot be as compare condition", col.Name, fieldValue.Interface()) + } + requiredField := useAllCols if b, ok := getFlagForColumn(mustColumnMap, col); ok { if b { @@ -910,7 +916,7 @@ func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, i func (statement *Statement) mergeConds(bean interface{}) error { if !statement.NoAutoCondition && statement.RefTable != nil { - var addedTableName = (len(statement.JoinStr) > 0) + addedTableName := (len(statement.JoinStr) > 0) autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName) if err != nil { return err @@ -948,7 +954,7 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string, switch sqlOrArgs[0].(type) { case string: if len(sqlOrArgs) > 1 { - var newArgs = make([]interface{}, 0, len(sqlOrArgs)-1) + newArgs := make([]interface{}, 0, len(sqlOrArgs)-1) for _, arg := range sqlOrArgs[1:] { if v, ok := arg.(time.Time); ok { newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05")) @@ -972,7 +978,7 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string, } func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName bool) string { - var colnames = make([]string, len(cols)) + colnames := make([]string, len(cols)) for i, col := range cols { if includeTableName { colnames[i] = statement.quote(statement.TableName()) + @@ -986,7 +992,7 @@ func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName // CondDeleted returns the conditions whether a record is soft deleted. func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { - var colName = statement.quote(col.Name) + colName := statement.quote(col.Name) if statement.JoinStr != "" { var prefix string if statement.TableAlias != "" { @@ -996,7 +1002,7 @@ func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { } colName = statement.quote(prefix) + "." + statement.quote(col.Name) } - var cond = builder.NewCond() + cond := builder.NewCond() if col.SQLType.IsNumeric() { cond = builder.Eq{colName: 0} } else { From 26d291bbc311274388045768386f015a06b3be10 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 24 Apr 2022 19:34:27 +0800 Subject: [PATCH 160/179] Add interface to allow structs to provide specific index information (#2137) The current mechanism for adding information about indices cannot express the ordering of the columns in the index or add sorting information. Here we add a new interface TableIndices which a struct would implement to provide a slice of *schema.Index to provide additional indices to that gleaned from the tags. Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/2137 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- integrations/tags_test.go | 108 ++++++++++++++++++++++++++++---------- tags/parser.go | 58 +++++++++++++++++--- 2 files changed, 132 insertions(+), 34 deletions(-) diff --git a/integrations/tags_test.go b/integrations/tags_test.go index 247a64e8..4c33d56c 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -165,7 +165,7 @@ func TestExtends(t *testing.T) { assert.True(t, info2.Userinfo.Uid > 0, "all of the id should has value") assert.True(t, info2.Userdetail.Id > 0, "all of the id should has value") - var infos2 = make([]UserAndDetail, 0) + infos2 := make([]UserAndDetail, 0) err = testEngine.Table(&Userinfo{}). Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). NoCascade(). @@ -219,9 +219,9 @@ func TestExtends2(t *testing.T) { err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) assert.NoError(t, err) - var sender = MessageUser{Name: "sender"} - var receiver = MessageUser{Name: "receiver"} - var msgtype = MessageType{Name: "type"} + sender := MessageUser{Name: "sender"} + receiver := MessageUser{Name: "receiver"} + msgtype := MessageType{Name: "type"} _, err = testEngine.Insert(&sender, &receiver, &msgtype) assert.NoError(t, err) @@ -254,8 +254,8 @@ func TestExtends2(t *testing.T) { assert.NoError(t, err) } - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote + mapper := testEngine.GetTableMapper().Obj2Table + quote := testEngine.Quote userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) msgTableName := quote(testEngine.TableName(mapper("Message"), true)) @@ -280,9 +280,9 @@ func TestExtends3(t *testing.T) { err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) assert.NoError(t, err) - var sender = MessageUser{Name: "sender"} - var receiver = MessageUser{Name: "receiver"} - var msgtype = MessageType{Name: "type"} + sender := MessageUser{Name: "sender"} + receiver := MessageUser{Name: "receiver"} + msgtype := MessageType{Name: "type"} _, err = testEngine.Insert(&sender, &receiver, &msgtype) assert.NoError(t, err) @@ -314,8 +314,8 @@ func TestExtends3(t *testing.T) { assert.NoError(t, err) } - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote + mapper := testEngine.GetTableMapper().Obj2Table + quote := testEngine.Quote userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) msgTableName := quote(testEngine.TableName(mapper("Message"), true)) @@ -345,8 +345,8 @@ func TestExtends4(t *testing.T) { err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) assert.NoError(t, err) - var sender = MessageUser{Name: "sender"} - var msgtype = MessageType{Name: "type"} + sender := MessageUser{Name: "sender"} + msgtype := MessageType{Name: "type"} _, err = testEngine.Insert(&sender, &msgtype) assert.NoError(t, err) @@ -377,8 +377,8 @@ func TestExtends4(t *testing.T) { assert.NoError(t, err) } - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote + mapper := testEngine.GetTableMapper().Obj2Table + quote := testEngine.Quote userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) msgTableName := quote(testEngine.TableName(mapper("Message"), true)) @@ -417,29 +417,29 @@ func TestExtends5(t *testing.T) { err = testEngine.CreateTables(&Size{}, &Book{}) assert.NoError(t, err) - var sc = Size{Width: 0.2, Height: 0.4} - var so = Size{Width: 0.2, Height: 0.8} - var s = Size{Width: 0.15, Height: 1.5} - var bk1 = Book{ + sc := Size{Width: 0.2, Height: 0.4} + so := Size{Width: 0.2, Height: 0.8} + s := Size{Width: 0.15, Height: 1.5} + bk1 := Book{ SizeOpen: &so, SizeClosed: &sc, Size: &s, } - var bk2 = Book{ + bk2 := Book{ SizeOpen: &so, } - var bk3 = Book{ + bk3 := Book{ SizeClosed: &sc, Size: &s, } - var bk4 = Book{} - var bk5 = Book{Size: &s} + bk4 := Book{} + bk5 := Book{Size: &s} _, err = testEngine.Insert(&sc, &so, &s, &bk1, &bk2, &bk3, &bk4, &bk5) if err != nil { t.Fatal(err) } - var books = map[int64]Book{ + books := map[int64]Book{ bk1.ID: bk1, bk2.ID: bk2, bk3.ID: bk3, @@ -450,8 +450,8 @@ func TestExtends5(t *testing.T) { session := testEngine.NewSession() defer session.Close() - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote + mapper := testEngine.GetTableMapper().Obj2Table + quote := testEngine.Quote bookTableName := quote(testEngine.TableName(mapper("Book"), true)) sizeTableName := quote(testEngine.TableName(mapper("Size"), true)) @@ -1301,7 +1301,7 @@ func TestVersion2(t *testing.T) { err = testEngine.CreateTables(new(VersionS)) assert.NoError(t, err) - var vers = []VersionS{ + vers := []VersionS{ {Name: "sfsfdsfds"}, {Name: "xxxxx"}, } @@ -1359,7 +1359,7 @@ func TestVersion4(t *testing.T) { err = testEngine.CreateTables(new(VersionUintS)) assert.NoError(t, err) - var vers = []VersionUintS{ + vers := []VersionUintS{ {Name: "sfsfdsfds"}, {Name: "xxxxx"}, } @@ -1400,3 +1400,55 @@ func TestIndexes(t *testing.T) { assert.EqualValues(t, slice1, slice2) assert.EqualValues(t, 3, len(tables[0].Indexes)) } + +type TestTableIndicesStruct struct { + Id int64 + Name string `xorm:"index index(f_one_f_two) unique(s)"` // we're going to override the index f_one_f_two in TableIndices and remove it from this column + Email string `xorm:"index unique(s)"` + FTwo string `xorm:"index(f_two_f_one) index(f_one_f_two) f_two"` + FOne string `xorm:"index(f_two_f_one) f_one"` +} + +func (t *TestTableIndicesStruct) TableIndices() []*schemas.Index { + newIndex := schemas.NewIndex("f_one_f_two", schemas.IndexType) + newIndex.AddColumn("f_one", "f_two") + + return []*schemas.Index{newIndex} +} + +func TestTableIndices(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + assertSync(t, new(TestTableIndicesStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 5, len(tables[0].Columns())) + slice1 := []string{ + testEngine.GetColumnMapper().Obj2Table("Id"), + testEngine.GetColumnMapper().Obj2Table("Name"), + testEngine.GetColumnMapper().Obj2Table("Email"), + testEngine.GetColumnMapper().Obj2Table("FTwo"), + testEngine.GetColumnMapper().Obj2Table("FOne"), + } + slice2 := []string{ + tables[0].Columns()[0].Name, + tables[0].Columns()[1].Name, + tables[0].Columns()[2].Name, + tables[0].Columns()[3].Name, + tables[0].Columns()[4].Name, + } + sort.Strings(slice1) + sort.Strings(slice2) + assert.EqualValues(t, slice1, slice2) + assert.EqualValues(t, 5, len(tables[0].Indexes)) + index, ok := tables[0].Indexes["f_one_f_two"] + if assert.True(t, ok) { + assert.EqualValues(t, []string{"f_one", "f_two"}, index.Cols) + } + index, ok = tables[0].Indexes["f_two_f_one"] + if assert.True(t, ok) { + assert.EqualValues(t, []string{"f_two", "f_one"}, index.Cols) + } +} diff --git a/tags/parser.go b/tags/parser.go index 83026862..028f8d0b 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -21,10 +21,15 @@ import ( "xorm.io/xorm/schemas" ) -var ( - // ErrUnsupportedType represents an unsupported type error - ErrUnsupportedType = errors.New("unsupported type") -) +// ErrUnsupportedType represents an unsupported type error +var ErrUnsupportedType = errors.New("unsupported type") + +// TableIndices is an interface that describes structs that provide additional index information above that which is automatically parsed +type TableIndices interface { + TableIndices() []*schemas.Index +} + +var tpTableIndices = reflect.TypeOf((*TableIndices)(nil)).Elem() // Parser represents a parser for xorm tag type Parser struct { @@ -177,7 +182,7 @@ func (parser *Parser) parseFieldWithNoTag(fieldIndex int, field reflect.StructFi } func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, field reflect.StructField, fieldValue reflect.Value, tags []tag) (*schemas.Column, error) { - var col = &schemas.Column{ + col := &schemas.Column{ FieldName: field.Name, FieldIndex: []int{fieldIndex}, Nullable: true, @@ -188,7 +193,7 @@ func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, f DefaultIsEmpty: true, } - var ctx = Context{ + ctx := Context{ table: table, col: col, fieldValue: fieldValue, @@ -329,5 +334,46 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { table.AddColumn(col) } // end for + indices := tableIndices(v) + for _, index := range indices { + // Override old information + if oldIndex, ok := table.Indexes[index.Name]; ok { + for _, colName := range oldIndex.Cols { + col := table.GetColumn(colName) + if col == nil { + return nil, ErrUnsupportedType + } + delete(col.Indexes, index.Name) + } + } + table.AddIndex(index) + for _, colName := range index.Cols { + col := table.GetColumn(colName) + if col == nil { + return nil, ErrUnsupportedType + } + col.Indexes[index.Name] = index.Type + } + } + return table, nil } + +func tableIndices(v reflect.Value) []*schemas.Index { + if v.Type().Implements(tpTableIndices) { + return v.Interface().(TableIndices).TableIndices() + } + + if v.Kind() == reflect.Ptr { + v = v.Elem() + if v.Type().Implements(tpTableIndices) { + return v.Interface().(TableIndices).TableIndices() + } + } else if v.CanAddr() { + v1 := v.Addr() + if v1.Type().Implements(tpTableIndices) { + return v1.Interface().(TableIndices).TableIndices() + } + } + return nil +} From 60540cbabee50b897803ff700f3629225713f8d6 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Mon, 30 May 2022 00:29:03 +0800 Subject: [PATCH 161/179] Remove const insertSelectPlaceholder and associated dead code (#2151) `insertSelectPlaceholder` is an unexported const set at true. No code changes this nor can any build environment change it. Therefore we should remove it and the associated dead code. Close #2146 Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/2151 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- internal/statements/statement_args.go | 94 +++------------------------ 1 file changed, 8 insertions(+), 86 deletions(-) diff --git a/internal/statements/statement_args.go b/internal/statements/statement_args.go index 64089c1e..727d5977 100644 --- a/internal/statements/statement_args.go +++ b/internal/statements/statement_args.go @@ -5,78 +5,10 @@ package statements import ( - "fmt" - "reflect" - "strings" - "time" - "xorm.io/builder" "xorm.io/xorm/schemas" ) -func quoteNeeded(a interface{}) bool { - switch a.(type) { - case int, int8, int16, int32, int64: - return false - case uint, uint8, uint16, uint32, uint64: - return false - case float32, float64: - return false - case bool: - return false - case string: - return true - case time.Time, *time.Time: - return true - case builder.Builder, *builder.Builder: - return false - } - - t := reflect.TypeOf(a) - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return false - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return false - case reflect.Float32, reflect.Float64: - return false - case reflect.Bool: - return false - case reflect.String: - return true - } - - return true -} - -func convertStringSingleQuote(arg string) string { - return "'" + strings.Replace(arg, "'", "''", -1) + "'" -} - -func convertString(arg string) string { - var buf strings.Builder - buf.WriteRune('\'') - for _, c := range arg { - if c == '\\' || c == '\'' { - buf.WriteRune('\\') - } - buf.WriteRune(c) - } - buf.WriteRune('\'') - return buf.String() -} - -func convertArg(arg interface{}, convertFunc func(string) string) string { - if quoteNeeded(arg) { - argv := fmt.Sprintf("%v", arg) - return convertFunc(argv) - } - - return fmt.Sprintf("%v", arg) -} - -const insertSelectPlaceHolder = true - // WriteArg writes an arg func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { @@ -91,27 +23,17 @@ func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) er return err } default: - if insertSelectPlaceHolder { - if err := w.WriteByte('?'); err != nil { - return err - } - if v, ok := arg.(bool); ok && statement.dialect.URI().DBType == schemas.MSSQL { - if v { - w.Append(1) - } else { - w.Append(0) - } + if err := w.WriteByte('?'); err != nil { + return err + } + if v, ok := arg.(bool); ok && statement.dialect.URI().DBType == schemas.MSSQL { + if v { + w.Append(1) } else { - w.Append(arg) + w.Append(0) } } else { - var convertFunc = convertStringSingleQuote - if statement.dialect.URI().DBType == schemas.MYSQL { - convertFunc = convertString - } - if _, err := w.WriteString(convertArg(arg, convertFunc)); err != nil { - return err - } + w.Append(arg) } } return nil From eeb7fcf22cd39c074607643a5d258ce7ea882045 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Mon, 30 May 2022 18:36:23 +0800 Subject: [PATCH 162/179] Add ORDER BY SEQ_IN_INDEX to MySQL GetIndexes to Fix IndexTests (#2152) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2152 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/mysql.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/dialects/mysql.go b/dialects/mysql.go index 56ba66c7..82df04dd 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -179,11 +179,9 @@ func (db *mysql) Init(uri *URI) error { return db.Base.Init(db, uri) } -var ( - mysqlColAliases = map[string]string{ - "numeric": "decimal", - } -) +var mysqlColAliases = map[string]string{ + "numeric": "decimal", +} // Alias returns a alias of column func (db *mysql) Alias(col string) string { @@ -243,7 +241,7 @@ func (db *mysql) Features() *DialectFeatures { func (db *mysql) SetParams(params map[string]string) { rowFormat, ok := params["rowFormat"] if ok { - var t = strings.ToUpper(rowFormat) + t := strings.ToUpper(rowFormat) switch t { case "COMPACT": fallthrough @@ -562,11 +560,11 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = mysqlQuoter + q := mysqlQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = mysqlQuoter + q := mysqlQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -578,7 +576,7 @@ func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) { func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{db.uri.DBName, tableName} - s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? ORDER BY `SEQ_IN_INDEX`" rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { @@ -669,7 +667,7 @@ func (db *mysql) CreateTableSQL(ctx context.Context, queryer core.Queryer, table b.WriteString(table.StoreEngine) } - var charset = table.Charset + charset := table.Charset if len(charset) == 0 { charset = db.URI().Charset } From f9a6990ecb22a83eebd359944453dead0b72a8c5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 31 May 2022 11:00:28 +0800 Subject: [PATCH 163/179] Refactor orderby and support arguments (#2150) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2150 --- dialects/mysql.go | 2 +- engine.go | 12 +- go.mod | 2 +- go.sum | 4 +- integrations/session_find_test.go | 4 + interface.go | 2 +- internal/statements/cond.go | 111 +++++++++ internal/statements/join.go | 78 +++++++ internal/statements/order_by.go | 90 ++++++++ internal/statements/query.go | 302 +++++++++++++------------ internal/statements/select.go | 137 +++++++++++ internal/statements/statement.go | 362 ++---------------------------- internal/statements/table_name.go | 56 +++++ internal/utils/builder.go | 27 +++ session.go | 4 +- session_delete.go | 133 +++++------ session_find.go | 22 +- session_update.go | 97 ++++---- 18 files changed, 813 insertions(+), 632 deletions(-) create mode 100644 internal/statements/cond.go create mode 100644 internal/statements/join.go create mode 100644 internal/statements/order_by.go create mode 100644 internal/statements/select.go create mode 100644 internal/statements/table_name.go create mode 100644 internal/utils/builder.go diff --git a/dialects/mysql.go b/dialects/mysql.go index 82df04dd..31e7b788 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -400,7 +400,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName " `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, `CHARACTER_MAXIMUM_LENGTH`, " + alreadyQuoted + " AS NEEDS_QUOTE " + "FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + - " ORDER BY `COLUMNS`.ORDINAL_POSITION" + " ORDER BY `COLUMNS`.ORDINAL_POSITION ASC" rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { diff --git a/engine.go b/engine.go index b7dcf5a2..81cfc7a9 100644 --- a/engine.go +++ b/engine.go @@ -380,7 +380,7 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { seq = 0 } } - var colName = strings.Trim(parts[0], `"`) + colName := strings.Trim(parts[0], `"`) if col := table.GetColumn(colName); col != nil { col.Indexes[index.Name] = index.Type } else { @@ -502,9 +502,9 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w } } - var dstTableName = dstTable.Name - var quoter = dstDialect.Quoter().Quote - var quotedDstTableName = quoter(dstTable.Name) + dstTableName := dstTable.Name + quoter := dstDialect.Quoter().Quote + quotedDstTableName := quoter(dstTable.Name) if dstDialect.URI().Schema != "" { dstTableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, dstTable.Name) quotedDstTableName = fmt.Sprintf("%s.%s", quoter(dstDialect.URI().Schema), quoter(dstTable.Name)) @@ -1006,10 +1006,10 @@ func (engine *Engine) Asc(colNames ...string) *Session { } // OrderBy will generate "ORDER BY order" -func (engine *Engine) OrderBy(order string) *Session { +func (engine *Engine) OrderBy(order interface{}, args ...interface{}) *Session { session := engine.NewSession() session.isAutoClose = true - return session.OrderBy(order) + return session.OrderBy(order, args...) } // Prepare enables prepare statement diff --git a/go.mod b/go.mod index 0764d73a..7bde41ae 100644 --- a/go.mod +++ b/go.mod @@ -17,5 +17,5 @@ require ( github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 modernc.org/sqlite v1.14.2 - xorm.io/builder v0.3.9 + xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 ) diff --git a/go.sum b/go.sum index 8e7ac44b..8bdc9798 100644 --- a/go.sum +++ b/go.sum @@ -659,5 +659,5 @@ modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao= modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= -xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= -xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 7f42d096..6701b1b5 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -247,6 +247,10 @@ func TestOrder(t *testing.T) { users2 := make([]Userinfo, 0) err = testEngine.Asc("id", "username").Desc("height").Find(&users2) assert.NoError(t, err) + + users = make([]Userinfo, 0) + err = testEngine.OrderBy("CASE WHEN username LIKE ? THEN 0 ELSE 1 END DESC", "a").Find(&users) + assert.NoError(t, err) } func TestGroupBy(t *testing.T) { diff --git a/interface.go b/interface.go index b9e88505..55ffebe4 100644 --- a/interface.go +++ b/interface.go @@ -54,7 +54,7 @@ type Interface interface { Nullable(...string) *Session Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session Omit(columns ...string) *Session - OrderBy(order string) *Session + OrderBy(order interface{}, args ...interface{}) *Session Ping() error Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) diff --git a/internal/statements/cond.go b/internal/statements/cond.go new file mode 100644 index 00000000..dfc6c208 --- /dev/null +++ b/internal/statements/cond.go @@ -0,0 +1,111 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +type QuoteReplacer struct { + *builder.BytesWriter + quoter schemas.Quoter +} + +func (q *QuoteReplacer) Write(p []byte) (n int, err error) { + c := q.quoter.Replace(string(p)) + return q.BytesWriter.Builder.WriteString(c) +} + +func (statement *Statement) QuoteReplacer(w *builder.BytesWriter) *QuoteReplacer { + return &QuoteReplacer{ + BytesWriter: w, + quoter: statement.dialect.Quoter(), + } +} + +// Where add Where statement +func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement { + return statement.And(query, args...) +} + +// And add Where & and statement +func (statement *Statement) And(query interface{}, args ...interface{}) *Statement { + switch qr := query.(type) { + case string: + cond := builder.Expr(qr, args...) + statement.cond = statement.cond.And(cond) + case map[string]interface{}: + cond := make(builder.Eq) + for k, v := range qr { + cond[statement.quote(k)] = v + } + statement.cond = statement.cond.And(cond) + case builder.Cond: + statement.cond = statement.cond.And(qr) + for _, v := range args { + if vv, ok := v.(builder.Cond); ok { + statement.cond = statement.cond.And(vv) + } + } + default: + statement.LastError = ErrConditionType + } + + return statement +} + +// Or add Where & Or statement +func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement { + switch qr := query.(type) { + case string: + cond := builder.Expr(qr, args...) + statement.cond = statement.cond.Or(cond) + case map[string]interface{}: + cond := make(builder.Eq) + for k, v := range qr { + cond[statement.quote(k)] = v + } + statement.cond = statement.cond.Or(cond) + case builder.Cond: + statement.cond = statement.cond.Or(qr) + for _, v := range args { + if vv, ok := v.(builder.Cond); ok { + statement.cond = statement.cond.Or(vv) + } + } + default: + statement.LastError = ErrConditionType + } + return statement +} + +// In generate "Where column IN (?) " statement +func (statement *Statement) In(column string, args ...interface{}) *Statement { + in := builder.In(statement.quote(column), args...) + statement.cond = statement.cond.And(in) + return statement +} + +// NotIn generate "Where column NOT IN (?) " statement +func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { + notIn := builder.NotIn(statement.quote(column), args...) + statement.cond = statement.cond.And(notIn) + return statement +} + +// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function +func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { + statement.NoAutoCondition = true + if len(no) > 0 { + statement.NoAutoCondition = no[0] + } + return statement +} + +// Conds returns condtions +func (statement *Statement) Conds() builder.Cond { + return statement.cond +} diff --git a/internal/statements/join.go b/internal/statements/join.go new file mode 100644 index 00000000..45fc2441 --- /dev/null +++ b/internal/statements/join.go @@ -0,0 +1,78 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" + "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/utils" + "xorm.io/xorm/schemas" +) + +// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN +func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { + var buf strings.Builder + if len(statement.JoinStr) > 0 { + fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) + } else { + fmt.Fprintf(&buf, "%v JOIN ", joinOP) + } + + switch tp := tablename.(type) { + case builder.Builder: + subSQL, subQueryArgs, err := tp.ToSQL() + if err != nil { + statement.LastError = err + return statement + } + + fields := strings.Split(tp.TableName(), ".") + aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) + aliasName = schemas.CommonQuoter.Trim(aliasName) + + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) + statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + case *builder.Builder: + subSQL, subQueryArgs, err := tp.ToSQL() + if err != nil { + statement.LastError = err + return statement + } + + fields := strings.Split(tp.TableName(), ".") + aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) + aliasName = schemas.CommonQuoter.Trim(aliasName) + + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) + statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + default: + tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) + if !utils.IsSubQuery(tbName) { + var buf strings.Builder + _ = statement.dialect.Quoter().QuoteTo(&buf, tbName) + tbName = buf.String() + } else { + tbName = statement.ReplaceQuote(tbName) + } + fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) + } + + statement.JoinStr = buf.String() + statement.joinArgs = append(statement.joinArgs, args...) + return statement +} + +func (statement *Statement) writeJoin(w builder.Writer) error { + if statement.JoinStr != "" { + if _, err := fmt.Fprint(w, " ", statement.JoinStr); err != nil { + return err + } + w.Append(statement.joinArgs...) + } + return nil +} diff --git a/internal/statements/order_by.go b/internal/statements/order_by.go new file mode 100644 index 00000000..08a8263b --- /dev/null +++ b/internal/statements/order_by.go @@ -0,0 +1,90 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" +) + +func (statement *Statement) HasOrderBy() bool { + return statement.orderStr != "" +} + +// ResetOrderBy reset ordery conditions +func (statement *Statement) ResetOrderBy() { + statement.orderStr = "" + statement.orderArgs = nil +} + +// WriteOrderBy write order by to writer +func (statement *Statement) WriteOrderBy(w builder.Writer) error { + if len(statement.orderStr) > 0 { + if _, err := fmt.Fprintf(w, " ORDER BY %s", statement.orderStr); err != nil { + return err + } + w.Append(statement.orderArgs...) + } + return nil +} + +// OrderBy generate "Order By order" statement +func (statement *Statement) OrderBy(order interface{}, args ...interface{}) *Statement { + if len(statement.orderStr) > 0 { + statement.orderStr += ", " + } + var rawOrder string + switch t := order.(type) { + case (*builder.Expression): + rawOrder = t.Content() + args = t.Args() + case string: + rawOrder = t + default: + statement.LastError = ErrUnSupportedSQLType + return statement + } + statement.orderStr += statement.ReplaceQuote(rawOrder) + if len(args) > 0 { + statement.orderArgs = append(statement.orderArgs, args...) + } + return statement +} + +// Desc generate `ORDER BY xx DESC` +func (statement *Statement) Desc(colNames ...string) *Statement { + var buf strings.Builder + if len(statement.orderStr) > 0 { + fmt.Fprint(&buf, statement.orderStr, ", ") + } + for i, col := range colNames { + if i > 0 { + fmt.Fprint(&buf, ", ") + } + _ = statement.dialect.Quoter().QuoteTo(&buf, col) + fmt.Fprint(&buf, " DESC") + } + statement.orderStr = buf.String() + return statement +} + +// Asc provide asc order by query condition, the input parameters are columns. +func (statement *Statement) Asc(colNames ...string) *Statement { + var buf strings.Builder + if len(statement.orderStr) > 0 { + fmt.Fprint(&buf, statement.orderStr, ", ") + } + for i, col := range colNames { + if i > 0 { + fmt.Fprint(&buf, ", ") + } + _ = statement.dialect.Quoter().QuoteTo(&buf, col) + fmt.Fprint(&buf, " ASC") + } + statement.orderStr = buf.String() + return statement +} diff --git a/internal/statements/query.go b/internal/statements/query.go index 8b383866..f72c8602 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -11,6 +11,7 @@ import ( "strings" "xorm.io/builder" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -28,7 +29,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return "", nil, ErrTableNotFound } - var columnStr = statement.ColumnStr() + columnStr := statement.ColumnStr() if len(statement.SelectStr) > 0 { columnStr = statement.SelectStr } else { @@ -58,19 +59,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return "", nil, err } - sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) - if err != nil { - return "", nil, err - } - args := append(statement.joinArgs, condArgs...) - - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - - return sqlStr, args, nil + return statement.genSelectSQL(columnStr, true, true) } // GenSumSQL generates sum SQL @@ -83,7 +72,7 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri return "", nil, err } - var sumStrs = make([]string, 0, len(columns)) + sumStrs := make([]string, 0, len(columns)) for _, colName := range columns { if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { colName = statement.quote(colName) @@ -94,16 +83,11 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri } sumSelect := strings.Join(sumStrs, ", ") - if err := statement.mergeConds(bean); err != nil { + if err := statement.MergeConds(bean); err != nil { return "", nil, err } - sqlStr, condArgs, err := statement.genSelectSQL(sumSelect, true, true) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil + return statement.genSelectSQL(sumSelect, true, true) } // GenGetSQL generates Get SQL @@ -119,7 +103,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, } } - var columnStr = statement.ColumnStr() + columnStr := statement.ColumnStr() if len(statement.SelectStr) > 0 { columnStr = statement.SelectStr } else { @@ -146,7 +130,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, } if isStruct { - if err := statement.mergeConds(bean); err != nil { + if err := statement.MergeConds(bean); err != nil { return "", nil, err } } else { @@ -155,12 +139,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, } } - sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil + return statement.genSelectSQL(columnStr, true, true) } // GenCountSQL generates the SQL for counting @@ -175,12 +154,12 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa if err := statement.SetRefBean(beans[0]); err != nil { return "", nil, err } - if err := statement.mergeConds(beans[0]); err != nil { + if err := statement.MergeConds(beans[0]); err != nil { return "", nil, err } } - var selectSQL = statement.SelectStr + selectSQL := statement.SelectStr if len(selectSQL) <= 0 { if statement.IsDistinct { selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr()) @@ -206,55 +185,58 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa sqlStr = fmt.Sprintf("SELECT %s FROM (%s) sub", selectSQL, sqlStr) } - return sqlStr, append(statement.joinArgs, condArgs...), nil + return sqlStr, condArgs, nil } -func (statement *Statement) fromBuilder() *strings.Builder { - var builder strings.Builder - var quote = statement.quote - var dialect = statement.dialect - - builder.WriteString(" FROM ") - - if dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { - builder.WriteString(statement.TableName()) - } else { - builder.WriteString(quote(statement.TableName())) +func (statement *Statement) writeFrom(w builder.Writer) error { + if _, err := fmt.Fprint(w, " FROM "); err != nil { + return err } + if err := statement.writeTableName(w); err != nil { + return err + } + if err := statement.writeAlias(w); err != nil { + return err + } + return statement.writeJoin(w) +} - if statement.TableAlias != "" { - if dialect.URI().DBType == schemas.ORACLE { - builder.WriteString(" ") - } else { - builder.WriteString(" AS ") +func (statement *Statement) writeLimitOffset(w builder.Writer) error { + if statement.Start > 0 { + if statement.LimitN != nil { + _, err := fmt.Fprintf(w, " LIMIT %v OFFSET %v", *statement.LimitN, statement.Start) + return err } - builder.WriteString(quote(statement.TableAlias)) + _, err := fmt.Fprintf(w, " LIMIT 0 OFFSET %v", statement.Start) + return err } - if statement.JoinStr != "" { - builder.WriteString(" ") - builder.WriteString(statement.JoinStr) + if statement.LimitN != nil { + _, err := fmt.Fprint(w, " LIMIT ", *statement.LimitN) + return err } - return &builder + // no limit statement + return nil } func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderBy bool) (string, []interface{}, error) { var ( - distinct string - dialect = statement.dialect - fromStr = statement.fromBuilder().String() - top, mssqlCondi, whereStr string + distinct string + dialect = statement.dialect + top, whereStr string + mssqlCondi = builder.NewWriter() ) if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { distinct = "DISTINCT " } - condSQL, condArgs, err := statement.GenCondSQL(statement.cond) - if err != nil { + condWriter := builder.NewWriter() + if err := statement.cond.WriteTo(statement.QuoteReplacer(condWriter)); err != nil { return "", nil, err } - if len(condSQL) > 0 { - whereStr = fmt.Sprintf(" WHERE %s", condSQL) + + if condWriter.Len() > 0 { + whereStr = " WHERE " } pLimitN := statement.LimitN @@ -289,49 +271,81 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB } } - var orderStr string - if needOrderBy && len(statement.OrderStr) > 0 { - orderStr = fmt.Sprintf(" ORDER BY %s", statement.OrderStr) + if _, err := fmt.Fprintf(mssqlCondi, "(%s NOT IN (SELECT TOP %d %s", + column, statement.Start, column); err != nil { + return "", nil, err } - - var groupStr string - if len(statement.GroupByStr) > 0 { - groupStr = fmt.Sprintf(" GROUP BY %s", statement.GroupByStr) + if err := statement.writeFrom(mssqlCondi); err != nil { + return "", nil, err + } + if whereStr != "" { + if _, err := fmt.Fprint(mssqlCondi, whereStr); err != nil { + return "", nil, err + } + if err := utils.WriteBuilder(mssqlCondi, statement.QuoteReplacer(condWriter)); err != nil { + return "", nil, err + } + } + if needOrderBy { + if err := statement.WriteOrderBy(mssqlCondi); err != nil { + return "", nil, err + } + } + if err := statement.WriteGroupBy(mssqlCondi); err != nil { + return "", nil, err + } + if _, err := fmt.Fprint(mssqlCondi, "))"); err != nil { + return "", nil, err } - mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s%s%s))", - column, statement.Start, column, fromStr, whereStr, orderStr, groupStr) } } - var buf strings.Builder - fmt.Fprintf(&buf, "SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr) - if len(mssqlCondi) > 0 { + buf := builder.NewWriter() + if _, err := fmt.Fprintf(buf, "SELECT %v%v%v", distinct, top, columnStr); err != nil { + return "", nil, err + } + if err := statement.writeFrom(buf); err != nil { + return "", nil, err + } + if whereStr != "" { + if _, err := fmt.Fprint(buf, whereStr); err != nil { + return "", nil, err + } + if err := utils.WriteBuilder(buf, statement.QuoteReplacer(condWriter)); err != nil { + return "", nil, err + } + } + if mssqlCondi.Len() > 0 { if len(whereStr) > 0 { - fmt.Fprint(&buf, " AND ", mssqlCondi) + if _, err := fmt.Fprint(buf, " AND "); err != nil { + return "", nil, err + } } else { - fmt.Fprint(&buf, " WHERE ", mssqlCondi) + if _, err := fmt.Fprint(buf, " WHERE "); err != nil { + return "", nil, err + } + } + + if err := utils.WriteBuilder(buf, mssqlCondi); err != nil { + return "", nil, err } } - if statement.GroupByStr != "" { - fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr) + if err := statement.WriteGroupBy(buf); err != nil { + return "", nil, err } - if statement.HavingStr != "" { - fmt.Fprint(&buf, " ", statement.HavingStr) + if err := statement.writeHaving(buf); err != nil { + return "", nil, err } - if needOrderBy && statement.OrderStr != "" { - fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) + if needOrderBy { + if err := statement.WriteOrderBy(buf); err != nil { + return "", nil, err + } } if needLimit { if dialect.URI().DBType != schemas.MSSQL && dialect.URI().DBType != schemas.ORACLE { - if statement.Start > 0 { - if pLimitN != nil { - fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", *pLimitN, statement.Start) - } else { - fmt.Fprintf(&buf, " LIMIT 0 OFFSET %v", statement.Start) - } - } else if pLimitN != nil { - fmt.Fprint(&buf, " LIMIT ", *pLimitN) + if err := statement.writeLimitOffset(buf); err != nil { + return "", nil, err } } else if dialect.URI().DBType == schemas.ORACLE { if pLimitN != nil { @@ -341,16 +355,16 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB if rawColStr == "*" { rawColStr = "at.*" } - fmt.Fprintf(&buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", + fmt.Fprintf(buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", columnStr, rawColStr, oldString, statement.Start+*pLimitN, statement.Start) } } } if statement.IsForUpdate { - return dialect.ForUpdateSQL(buf.String()), condArgs, nil + return dialect.ForUpdateSQL(buf.String()), buf.Args(), nil } - return buf.String(), condArgs, nil + return buf.String(), buf.Args(), nil } // GenExistSQL generates Exist SQL @@ -359,10 +373,6 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac return statement.GenRawSQL(), statement.RawParams, nil } - var sqlStr string - var args []interface{} - var joinStr string - var err error var b interface{} if len(bean) > 0 { b = bean[0] @@ -381,45 +391,70 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac if len(tableName) <= 0 { return "", nil, ErrTableNotFound } - if statement.RefTable == nil { - tableName = statement.quote(tableName) - if len(statement.JoinStr) > 0 { - joinStr = statement.JoinStr - } + if statement.RefTable != nil { + return statement.Limit(1).GenGetSQL(b) + } + tableName = statement.quote(tableName) + + buf := builder.NewWriter() + if statement.dialect.URI().DBType == schemas.MSSQL { + if _, err := fmt.Fprintf(buf, "SELECT TOP 1 * FROM %s", tableName); err != nil { + return "", nil, err + } + if err := statement.writeJoin(buf); err != nil { + return "", nil, err + } if statement.Conds().IsValid() { - condSQL, condArgs, err := statement.GenCondSQL(statement.Conds()) - if err != nil { + if _, err := fmt.Fprintf(buf, " WHERE "); err != nil { return "", nil, err } - - if statement.dialect.URI().DBType == schemas.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) - } else if statement.dialect.URI().DBType == schemas.ORACLE { - sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) %s AND ROWNUM=1", tableName, joinStr, condSQL) - } else { - sqlStr = fmt.Sprintf("SELECT 1 FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL) + if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil { + return "", nil, err } - args = condArgs - } else { - if statement.dialect.URI().DBType == schemas.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) - } else if statement.dialect.URI().DBType == schemas.ORACLE { - sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE ROWNUM=1", tableName, joinStr) - } else { - sqlStr = fmt.Sprintf("SELECT 1 FROM %s %s LIMIT 1", tableName, joinStr) + } + } else if statement.dialect.URI().DBType == schemas.ORACLE { + if _, err := fmt.Fprintf(buf, "SELECT * FROM %s", tableName); err != nil { + return "", nil, err + } + if err := statement.writeJoin(buf); err != nil { + return "", nil, err + } + if _, err := fmt.Fprintf(buf, " WHERE "); err != nil { + return "", nil, err + } + if statement.Conds().IsValid() { + if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil { + return "", nil, err } - args = []interface{}{} + if _, err := fmt.Fprintf(buf, " AND "); err != nil { + return "", nil, err + } + } + if _, err := fmt.Fprintf(buf, "ROWNUM=1"); err != nil { + return "", nil, err } } else { - statement.Limit(1) - sqlStr, args, err = statement.GenGetSQL(b) - if err != nil { + if _, err := fmt.Fprintf(buf, "SELECT 1 FROM %s", tableName); err != nil { + return "", nil, err + } + if err := statement.writeJoin(buf); err != nil { + return "", nil, err + } + if statement.Conds().IsValid() { + if _, err := fmt.Fprintf(buf, " WHERE "); err != nil { + return "", nil, err + } + if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil { + return "", nil, err + } + } + if _, err := fmt.Fprintf(buf, " LIMIT 1"); err != nil { return "", nil, err } } - return sqlStr, args, nil + return buf.String(), buf.Args(), nil } // GenFindSQL generates Find SQL @@ -428,15 +463,11 @@ func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interfa return statement.GenRawSQL(), statement.RawParams, nil } - var sqlStr string - var args []interface{} - var err error - if len(statement.TableName()) <= 0 { return "", nil, ErrTableNotFound } - var columnStr = statement.ColumnStr() + columnStr := statement.ColumnStr() if len(statement.SelectStr) > 0 { columnStr = statement.SelectStr } else { @@ -464,16 +495,5 @@ func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interfa statement.cond = statement.cond.And(autoCond) - sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) - if err != nil { - return "", nil, err - } - args = append(statement.joinArgs, condArgs...) - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - - return sqlStr, args, nil + return statement.genSelectSQL(columnStr, true, true) } diff --git a/internal/statements/select.go b/internal/statements/select.go new file mode 100644 index 00000000..2bd2e94d --- /dev/null +++ b/internal/statements/select.go @@ -0,0 +1,137 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/xorm/schemas" +) + +// Select replace select +func (statement *Statement) Select(str string) *Statement { + statement.SelectStr = statement.ReplaceQuote(str) + return statement +} + +func col2NewCols(columns ...string) []string { + newColumns := make([]string, 0, len(columns)) + for _, col := range columns { + col = strings.Replace(col, "`", "", -1) + col = strings.Replace(col, `"`, "", -1) + ccols := strings.Split(col, ",") + for _, c := range ccols { + newColumns = append(newColumns, strings.TrimSpace(c)) + } + } + return newColumns +} + +// Cols generate "col1, col2" statement +func (statement *Statement) Cols(columns ...string) *Statement { + cols := col2NewCols(columns...) + for _, nc := range cols { + statement.ColumnMap.Add(nc) + } + return statement +} + +// ColumnStr returns column string +func (statement *Statement) ColumnStr() string { + return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") +} + +// AllCols update use only: update all columns +func (statement *Statement) AllCols() *Statement { + statement.useAllCols = true + return statement +} + +// MustCols update use only: must update columns +func (statement *Statement) MustCols(columns ...string) *Statement { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.MustColumnMap[strings.ToLower(nc)] = true + } + return statement +} + +// UseBool indicates that use bool fields as update contents and query contiditions +func (statement *Statement) UseBool(columns ...string) *Statement { + if len(columns) > 0 { + statement.MustCols(columns...) + } else { + statement.allUseBool = true + } + return statement +} + +// Omit do not use the columns +func (statement *Statement) Omit(columns ...string) { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.OmitColumnMap = append(statement.OmitColumnMap, nc) + } +} + +func (statement *Statement) genColumnStr() string { + if statement.RefTable == nil { + return "" + } + + var buf strings.Builder + columns := statement.RefTable.Columns() + + for _, col := range columns { + if statement.OmitColumnMap.Contain(col.Name) { + continue + } + + if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) { + continue + } + + if col.MapType == schemas.ONLYTODB { + continue + } + + if buf.Len() != 0 { + buf.WriteString(", ") + } + + if statement.JoinStr != "" { + if statement.TableAlias != "" { + buf.WriteString(statement.TableAlias) + } else { + buf.WriteString(statement.TableName()) + } + + buf.WriteString(".") + } + + statement.dialect.Quoter().QuoteTo(&buf, col.Name) + } + + return buf.String() +} + +func (statement *Statement) colName(col *schemas.Column, tableName string) string { + if statement.needTableName() { + nm := tableName + if len(statement.TableAlias) > 0 { + nm = statement.TableAlias + } + return fmt.Sprintf("%s.%s", statement.quote(nm), statement.quote(col.Name)) + } + return statement.quote(col.Name) +} + +// Distinct generates "DISTINCT col1, col2 " statement +func (statement *Statement) Distinct(columns ...string) *Statement { + statement.IsDistinct = true + statement.Cols(columns...) + return statement +} diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 3069561e..a8fe34fa 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -43,7 +43,8 @@ type Statement struct { Start int LimitN *int idParam schemas.PK - OrderStr string + orderStr string + orderArgs []interface{} JoinStr string joinArgs []interface{} GroupByStr string @@ -101,15 +102,6 @@ func (statement *Statement) GenRawSQL() string { return statement.ReplaceQuote(statement.RawSQL) } -// GenCondSQL generates condition SQL -func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []interface{}, error) { - condSQL, condArgs, err := builder.ToSQL(condOrBuilder) - if err != nil { - return "", nil, err - } - return statement.ReplaceQuote(condSQL), condArgs, nil -} - // ReplaceQuote replace sql key words with quote func (statement *Statement) ReplaceQuote(sql string) string { if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL || @@ -129,7 +121,7 @@ func (statement *Statement) Reset() { statement.RefTable = nil statement.Start = 0 statement.LimitN = nil - statement.OrderStr = "" + statement.ResetOrderBy() statement.UseCascade = true statement.JoinStr = "" statement.joinArgs = make([]interface{}, 0) @@ -164,21 +156,6 @@ func (statement *Statement) Reset() { statement.LastError = nil } -// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function -func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { - statement.NoAutoCondition = true - if len(no) > 0 { - statement.NoAutoCondition = no[0] - } - return statement -} - -// Alias set the table alias -func (statement *Statement) Alias(alias string) *Statement { - statement.TableAlias = alias - return statement -} - // SQL adds raw sql statement func (statement *Statement) SQL(query interface{}, args ...interface{}) *Statement { switch query.(type) { @@ -198,80 +175,10 @@ func (statement *Statement) SQL(query interface{}, args ...interface{}) *Stateme return statement } -// Where add Where statement -func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement { - return statement.And(query, args...) -} - func (statement *Statement) quote(s string) string { return statement.dialect.Quoter().Quote(s) } -// And add Where & and statement -func (statement *Statement) And(query interface{}, args ...interface{}) *Statement { - switch qr := query.(type) { - case string: - cond := builder.Expr(qr, args...) - statement.cond = statement.cond.And(cond) - case map[string]interface{}: - cond := make(builder.Eq) - for k, v := range qr { - cond[statement.quote(k)] = v - } - statement.cond = statement.cond.And(cond) - case builder.Cond: - statement.cond = statement.cond.And(qr) - for _, v := range args { - if vv, ok := v.(builder.Cond); ok { - statement.cond = statement.cond.And(vv) - } - } - default: - statement.LastError = ErrConditionType - } - - return statement -} - -// Or add Where & Or statement -func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement { - switch qr := query.(type) { - case string: - cond := builder.Expr(qr, args...) - statement.cond = statement.cond.Or(cond) - case map[string]interface{}: - cond := make(builder.Eq) - for k, v := range qr { - cond[statement.quote(k)] = v - } - statement.cond = statement.cond.Or(cond) - case builder.Cond: - statement.cond = statement.cond.Or(qr) - for _, v := range args { - if vv, ok := v.(builder.Cond); ok { - statement.cond = statement.cond.Or(vv) - } - } - default: - statement.LastError = ErrConditionType - } - return statement -} - -// In generate "Where column IN (?) " statement -func (statement *Statement) In(column string, args ...interface{}) *Statement { - in := builder.In(statement.quote(column), args...) - statement.cond = statement.cond.And(in) - return statement -} - -// NotIn generate "Where column NOT IN (?) " statement -func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { - notIn := builder.NotIn(statement.quote(column), args...) - statement.cond = statement.cond.And(notIn) - return statement -} - // SetRefValue set ref value func (statement *Statement) SetRefValue(v reflect.Value) error { var err error @@ -302,26 +209,6 @@ func (statement *Statement) needTableName() bool { return len(statement.JoinStr) > 0 } -func (statement *Statement) colName(col *schemas.Column, tableName string) string { - if statement.needTableName() { - nm := tableName - if len(statement.TableAlias) > 0 { - nm = statement.TableAlias - } - return fmt.Sprintf("%s.%s", statement.quote(nm), statement.quote(col.Name)) - } - return statement.quote(col.Name) -} - -// TableName return current tableName -func (statement *Statement) TableName() string { - if statement.AltTableName != "" { - return statement.AltTableName - } - - return statement.tableName -} - // Incr Generate "Update ... Set column = column + arg" statement func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { if len(arg) > 0 { @@ -352,85 +239,12 @@ func (statement *Statement) SetExpr(column string, expression interface{}) *Stat return statement } -// Distinct generates "DISTINCT col1, col2 " statement -func (statement *Statement) Distinct(columns ...string) *Statement { - statement.IsDistinct = true - statement.Cols(columns...) - return statement -} - // ForUpdate generates "SELECT ... FOR UPDATE" statement func (statement *Statement) ForUpdate() *Statement { statement.IsForUpdate = true return statement } -// Select replace select -func (statement *Statement) Select(str string) *Statement { - statement.SelectStr = statement.ReplaceQuote(str) - return statement -} - -func col2NewCols(columns ...string) []string { - newColumns := make([]string, 0, len(columns)) - for _, col := range columns { - col = strings.Replace(col, "`", "", -1) - col = strings.Replace(col, `"`, "", -1) - ccols := strings.Split(col, ",") - for _, c := range ccols { - newColumns = append(newColumns, strings.TrimSpace(c)) - } - } - return newColumns -} - -// Cols generate "col1, col2" statement -func (statement *Statement) Cols(columns ...string) *Statement { - cols := col2NewCols(columns...) - for _, nc := range cols { - statement.ColumnMap.Add(nc) - } - return statement -} - -// ColumnStr returns column string -func (statement *Statement) ColumnStr() string { - return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") -} - -// AllCols update use only: update all columns -func (statement *Statement) AllCols() *Statement { - statement.useAllCols = true - return statement -} - -// MustCols update use only: must update columns -func (statement *Statement) MustCols(columns ...string) *Statement { - newColumns := col2NewCols(columns...) - for _, nc := range newColumns { - statement.MustColumnMap[strings.ToLower(nc)] = true - } - return statement -} - -// UseBool indicates that use bool fields as update contents and query contiditions -func (statement *Statement) UseBool(columns ...string) *Statement { - if len(columns) > 0 { - statement.MustCols(columns...) - } else { - statement.allUseBool = true - } - return statement -} - -// Omit do not use the columns -func (statement *Statement) Omit(columns ...string) { - newColumns := col2NewCols(columns...) - for _, nc := range newColumns { - statement.OmitColumnMap = append(statement.OmitColumnMap, nc) - } -} - // Nullable Update use only: update columns to null when value is nullable and zero-value func (statement *Statement) Nullable(columns ...string) { newColumns := col2NewCols(columns...) @@ -454,54 +268,6 @@ func (statement *Statement) Limit(limit int, start ...int) *Statement { return statement } -// OrderBy generate "Order By order" statement -func (statement *Statement) OrderBy(order string) *Statement { - if len(statement.OrderStr) > 0 { - statement.OrderStr += ", " - } - statement.OrderStr += statement.ReplaceQuote(order) - return statement -} - -// Desc generate `ORDER BY xx DESC` -func (statement *Statement) Desc(colNames ...string) *Statement { - var buf strings.Builder - if len(statement.OrderStr) > 0 { - fmt.Fprint(&buf, statement.OrderStr, ", ") - } - for i, col := range colNames { - if i > 0 { - fmt.Fprint(&buf, ", ") - } - _ = statement.dialect.Quoter().QuoteTo(&buf, col) - fmt.Fprint(&buf, " DESC") - } - statement.OrderStr = buf.String() - return statement -} - -// Asc provide asc order by query condition, the input parameters are columns. -func (statement *Statement) Asc(colNames ...string) *Statement { - var buf strings.Builder - if len(statement.OrderStr) > 0 { - fmt.Fprint(&buf, statement.OrderStr, ", ") - } - for i, col := range colNames { - if i > 0 { - fmt.Fprint(&buf, ", ") - } - _ = statement.dialect.Quoter().QuoteTo(&buf, col) - fmt.Fprint(&buf, " ASC") - } - statement.OrderStr = buf.String() - return statement -} - -// Conds returns condtions -func (statement *Statement) Conds() builder.Cond { - return statement.cond -} - // SetTable tempororily set table name, the parameter could be a string or a pointer of struct func (statement *Statement) SetTable(tableNameOrBean interface{}) error { v := rValue(tableNameOrBean) @@ -518,71 +284,34 @@ func (statement *Statement) SetTable(tableNameOrBean interface{}) error { return nil } -// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { - var buf strings.Builder - if len(statement.JoinStr) > 0 { - fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) - } else { - fmt.Fprintf(&buf, "%v JOIN ", joinOP) - } - - switch tp := tablename.(type) { - case builder.Builder: - subSQL, subQueryArgs, err := tp.ToSQL() - if err != nil { - statement.LastError = err - return statement - } - - fields := strings.Split(tp.TableName(), ".") - aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) - aliasName = schemas.CommonQuoter.Trim(aliasName) - - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) - case *builder.Builder: - subSQL, subQueryArgs, err := tp.ToSQL() - if err != nil { - statement.LastError = err - return statement - } - - fields := strings.Split(tp.TableName(), ".") - aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) - aliasName = schemas.CommonQuoter.Trim(aliasName) - - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) - default: - tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) - if !utils.IsSubQuery(tbName) { - var buf strings.Builder - _ = statement.dialect.Quoter().QuoteTo(&buf, tbName) - tbName = buf.String() - } else { - tbName = statement.ReplaceQuote(tbName) - } - fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) - } - - statement.JoinStr = buf.String() - statement.joinArgs = append(statement.joinArgs, args...) - return statement -} - // GroupBy generate "Group By keys" statement func (statement *Statement) GroupBy(keys string) *Statement { statement.GroupByStr = statement.ReplaceQuote(keys) return statement } +func (statement *Statement) WriteGroupBy(w builder.Writer) error { + if statement.GroupByStr == "" { + return nil + } + _, err := fmt.Fprintf(w, " GROUP BY %s", statement.GroupByStr) + return err +} + // Having generate "Having conditions" statement func (statement *Statement) Having(conditions string) *Statement { statement.HavingStr = fmt.Sprintf("HAVING %v", statement.ReplaceQuote(conditions)) return statement } +func (statement *Statement) writeHaving(w builder.Writer) error { + if statement.HavingStr == "" { + return nil + } + _, err := fmt.Fprint(w, " ", statement.HavingStr) + return err +} + // SetUnscoped always disable struct tag "deleted" func (statement *Statement) SetUnscoped() *Statement { statement.unscoped = true @@ -594,47 +323,6 @@ func (statement *Statement) GetUnscoped() bool { return statement.unscoped } -func (statement *Statement) genColumnStr() string { - if statement.RefTable == nil { - return "" - } - - var buf strings.Builder - columns := statement.RefTable.Columns() - - for _, col := range columns { - if statement.OmitColumnMap.Contain(col.Name) { - continue - } - - if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) { - continue - } - - if col.MapType == schemas.ONLYTODB { - continue - } - - if buf.Len() != 0 { - buf.WriteString(", ") - } - - if statement.JoinStr != "" { - if statement.TableAlias != "" { - buf.WriteString(statement.TableAlias) - } else { - buf.WriteString(statement.TableName()) - } - - buf.WriteString(".") - } - - statement.dialect.Quoter().QuoteTo(&buf, col.Name) - } - - return buf.String() -} - // GenIndexSQL generated create index SQL func (statement *Statement) GenIndexSQL() []string { var sqls []string @@ -914,7 +602,8 @@ func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, i statement.unscoped, statement.MustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) } -func (statement *Statement) mergeConds(bean interface{}) error { +// MergeConds merge conditions from bean and id +func (statement *Statement) MergeConds(bean interface{}) error { if !statement.NoAutoCondition && statement.RefTable != nil { addedTableName := (len(statement.JoinStr) > 0) autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName) @@ -927,15 +616,6 @@ func (statement *Statement) mergeConds(bean interface{}) error { return statement.ProcessIDParam() } -// GenConds generates conditions -func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, error) { - if err := statement.mergeConds(bean); err != nil { - return "", nil, err - } - - return statement.GenCondSQL(statement.cond) -} - func (statement *Statement) quoteColumnStr(columnStr string) string { columns := strings.Split(columnStr, ",") return statement.dialect.Quoter().Join(columns, ",") diff --git a/internal/statements/table_name.go b/internal/statements/table_name.go new file mode 100644 index 00000000..8072a99d --- /dev/null +++ b/internal/statements/table_name.go @@ -0,0 +1,56 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +// TableName return current tableName +func (statement *Statement) TableName() string { + if statement.AltTableName != "" { + return statement.AltTableName + } + + return statement.tableName +} + +// Alias set the table alias +func (statement *Statement) Alias(alias string) *Statement { + statement.TableAlias = alias + return statement +} + +func (statement *Statement) writeAlias(w builder.Writer) error { + if statement.TableAlias != "" { + if statement.dialect.URI().DBType == schemas.ORACLE { + if _, err := fmt.Fprint(w, " ", statement.quote(statement.TableAlias)); err != nil { + return err + } + } else { + if _, err := fmt.Fprint(w, " AS ", statement.quote(statement.TableAlias)); err != nil { + return err + } + } + } + return nil +} + +func (statement *Statement) writeTableName(w builder.Writer) error { + if statement.dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { + if _, err := fmt.Fprint(w, statement.TableName()); err != nil { + return err + } + } else { + if _, err := fmt.Fprint(w, statement.quote(statement.TableName())); err != nil { + return err + } + } + return nil +} diff --git a/internal/utils/builder.go b/internal/utils/builder.go new file mode 100644 index 00000000..bc97526f --- /dev/null +++ b/internal/utils/builder.go @@ -0,0 +1,27 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "fmt" + + "xorm.io/builder" +) + +type BuildReader interface { + String() string + Args() []interface{} +} + +// WriteBuilder writes writers to one +func WriteBuilder(w *builder.BytesWriter, inputs ...BuildReader) error { + for _, input := range inputs { + if _, err := fmt.Fprint(w, input.String()); err != nil { + return err + } + w.Append(input.Args()...) + } + return nil +} diff --git a/session.go b/session.go index 64b98bfe..388678cd 100644 --- a/session.go +++ b/session.go @@ -275,8 +275,8 @@ func (session *Session) Limit(limit int, start ...int) *Session { // OrderBy provide order by query condition, the input parameter is the content // after order by on a sql statement. -func (session *Session) OrderBy(order string) *Session { - session.statement.OrderBy(order) +func (session *Session) OrderBy(order interface{}, args ...interface{}) *Session { + session.statement.OrderBy(order, args...) return session } diff --git a/session_delete.go b/session_delete.go index a0f420b1..322d5a44 100644 --- a/session_delete.go +++ b/session_delete.go @@ -9,7 +9,9 @@ import ( "fmt" "strconv" + "xorm.io/builder" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -99,10 +101,9 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } var ( - condSQL string - condArgs []interface{} - err error - bean interface{} + condWriter = builder.NewWriter() + err error + bean interface{} ) if len(beans) > 0 { bean = beans[0] @@ -116,115 +117,97 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { processor.BeforeDelete() } - condSQL, condArgs, err = session.statement.GenConds(bean) - } else { - condSQL, condArgs, err = session.statement.GenCondSQL(session.statement.Conds()) + if err = session.statement.MergeConds(bean); err != nil { + return 0, err + } } - if err != nil { + + if err = session.statement.Conds().WriteTo(session.statement.QuoteReplacer(condWriter)); err != nil { return 0, err } pLimitN := session.statement.LimitN - if len(condSQL) == 0 && (pLimitN == nil || *pLimitN == 0) { + if condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { return 0, ErrNeedDeletedCond } - var tableNameNoQuote = session.statement.TableName() - var tableName = session.engine.Quote(tableNameNoQuote) - var table = session.statement.RefTable - var deleteSQL string - if len(condSQL) > 0 { - deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL) - } else { - deleteSQL = fmt.Sprintf("DELETE FROM %v", tableName) + tableNameNoQuote := session.statement.TableName() + tableName := session.engine.Quote(tableNameNoQuote) + table := session.statement.RefTable + deleteSQLWriter := builder.NewWriter() + fmt.Fprintf(deleteSQLWriter, "DELETE FROM %v", tableName) + if condWriter.Len() > 0 { + fmt.Fprintf(deleteSQLWriter, " WHERE %v", condWriter.String()) + deleteSQLWriter.Append(condWriter.Args()...) } - var orderSQL string - if len(session.statement.OrderStr) > 0 { - orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr) + orderSQLWriter := builder.NewWriter() + if err := session.statement.WriteOrderBy(orderSQLWriter); err != nil { + return 0, err } + if pLimitN != nil && *pLimitN > 0 { limitNValue := *pLimitN - orderSQL += fmt.Sprintf(" LIMIT %d", limitNValue) + if _, err := fmt.Fprintf(orderSQLWriter, " LIMIT %d", limitNValue); err != nil { + return 0, err + } } - if len(orderSQL) > 0 { + orderCondWriter := builder.NewWriter() + if orderSQLWriter.Len() > 0 { switch session.engine.dialect.URI().DBType { case schemas.POSTGRES: - inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - deleteSQL += " AND " + inSQL + if condWriter.Len() > 0 { + fmt.Fprintf(orderCondWriter, " AND ") } else { - deleteSQL += " WHERE " + inSQL + fmt.Fprintf(orderCondWriter, " WHERE ") } + fmt.Fprintf(orderCondWriter, "ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQLWriter.String()) + orderCondWriter.Append(orderSQLWriter.Args()...) case schemas.SQLITE: - inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - deleteSQL += " AND " + inSQL + if condWriter.Len() > 0 { + fmt.Fprintf(orderCondWriter, " AND ") } else { - deleteSQL += " WHERE " + inSQL + fmt.Fprintf(orderCondWriter, " WHERE ") } + fmt.Fprintf(orderCondWriter, "rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQLWriter.String()) // TODO: how to handle delete limit on mssql? case schemas.MSSQL: return 0, ErrNotImplemented default: - deleteSQL += orderSQL + fmt.Fprint(orderCondWriter, orderSQLWriter.String()) + orderCondWriter.Append(orderSQLWriter.Args()...) } } - var realSQL string - argsForCache := make([]interface{}, 0, len(condArgs)*2) + realSQLWriter := builder.NewWriter() + argsForCache := make([]interface{}, 0, len(deleteSQLWriter.Args())*2) + copy(argsForCache, deleteSQLWriter.Args()) + argsForCache = append(deleteSQLWriter.Args(), argsForCache...) if session.statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled - realSQL = deleteSQL - copy(argsForCache, condArgs) - argsForCache = append(condArgs, argsForCache...) + if err := utils.WriteBuilder(realSQLWriter, deleteSQLWriter, orderCondWriter); err != nil { + return 0, err + } } else { - // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for caches. - copy(argsForCache, condArgs) - argsForCache = append(condArgs, argsForCache...) - deletedColumn := table.DeletedColumn() - realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", + if _, err := fmt.Fprintf(realSQLWriter, "UPDATE %v SET %v = ? WHERE %v", session.engine.Quote(session.statement.TableName()), session.engine.Quote(deletedColumn.Name), - condSQL) - - if len(orderSQL) > 0 { - switch session.engine.dialect.URI().DBType { - case schemas.POSTGRES: - inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - realSQL += " AND " + inSQL - } else { - realSQL += " WHERE " + inSQL - } - case schemas.SQLITE: - inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - realSQL += " AND " + inSQL - } else { - realSQL += " WHERE " + inSQL - } - // TODO: how to handle delete limit on mssql? - case schemas.MSSQL: - return 0, ErrNotImplemented - default: - realSQL += orderSQL - } + condWriter.String()); err != nil { + return 0, err } - - // !oinume! Insert nowTime to the head of session.statement.Params - condArgs = append(condArgs, "") - paramsLen := len(condArgs) - copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) - val, t, err := session.engine.nowTime(deletedColumn) if err != nil { return 0, err } - condArgs[0] = val + realSQLWriter.Append(val) + realSQLWriter.Append(condWriter.Args()...) - var colName = deletedColumn.Name + if err := utils.WriteBuilder(realSQLWriter, orderCondWriter); err != nil { + return 0, err + } + + colName := deletedColumn.Name session.afterClosures = append(session.afterClosures, func(bean interface{}) { col := table.GetColumn(colName) setColumnTime(bean, col, t) @@ -232,11 +215,11 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } if cacher := session.engine.GetCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache { - _ = session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) + _ = session.cacheDelete(table, tableNameNoQuote, deleteSQLWriter.String(), argsForCache...) } session.statement.RefTable = table - res, err := session.exec(realSQL, condArgs...) + res, err := session.exec(realSQLWriter.String(), realSQLWriter.Args()...) if err != nil { return 0, err } diff --git a/session_find.go b/session_find.go index caf79ee3..2270454b 100644 --- a/session_find.go +++ b/session_find.go @@ -60,9 +60,7 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte if len(session.statement.ColumnMap) > 0 && !session.statement.IsDistinct { session.statement.ColumnMap = []string{} } - if session.statement.OrderStr != "" { - session.statement.OrderStr = "" - } + session.statement.ResetOrderBy() if session.statement.LimitN != nil { session.statement.LimitN = nil } @@ -85,15 +83,15 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) - var isSlice = sliceValue.Kind() == reflect.Slice - var isMap = sliceValue.Kind() == reflect.Map + isSlice := sliceValue.Kind() == reflect.Slice + isMap := sliceValue.Kind() == reflect.Map if !isSlice && !isMap { return errors.New("needs a pointer to a slice or a map") } sliceElementType := sliceValue.Type().Elem() - var tp = tpStruct + tp := tpStruct if session.statement.RefTable == nil { if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Elem().Kind() == reflect.Struct { @@ -190,7 +188,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return err } - var newElemFunc = func(fields []string) reflect.Value { + newElemFunc := func(fields []string) reflect.Value { return utils.New(elemType, len(fields), len(fields)) } @@ -235,7 +233,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } if elemType.Kind() == reflect.Struct { - var newValue = newElemFunc(fields) + newValue := newElemFunc(fields) tb, err := session.engine.tagParser.ParseWithCache(newValue) if err != nil { return err @@ -249,7 +247,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } for rows.Next() { - var newValue = newElemFunc(fields) + newValue := newElemFunc(fields) bean := newValue.Interface() switch elemType.Kind() { @@ -310,7 +308,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in session.engine.logger.Debugf("[cacheFind] ids length > 500, no cache") return ErrCacheFailed } - var res = make([]string, len(table.PrimaryKeys)) + res := make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { return err @@ -342,7 +340,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in ididxes := make(map[string]int) var ides []schemas.PK - var temps = make([]interface{}, len(ids)) + temps := make([]interface{}, len(ids)) for idx, id := range ids { sid, err := id.ToString() @@ -457,7 +455,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) } } else if sliceValue.Kind() == reflect.Map { - var key = ids[j] + key := ids[j] keyType := sliceValue.Type().Key() keyValue := reflect.New(keyType) var ikey interface{} diff --git a/session_update.go b/session_update.go index fefbee90..76f311d6 100644 --- a/session_update.go +++ b/session_update.go @@ -60,7 +60,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri ids = make([]schemas.PK, 0) for rows.Next() { - var res = make([]string, len(table.PrimaryKeys)) + res := make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { return err @@ -176,8 +176,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 // -- var err error - var isMap = t.Kind() == reflect.Map - var isStruct = t.Kind() == reflect.Struct + isMap := t.Kind() == reflect.Map + isStruct := t.Kind() == reflect.Struct if isStruct { if err := session.statement.SetRefBean(bean); err != nil { return 0, err @@ -226,7 +226,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 args = append(args, val) } - var colName = col.Name + colName := col.Name if isStruct { session.afterClosures = append(session.afterClosures, func(bean interface{}) { col := table.GetColumn(colName) @@ -258,10 +258,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } colNames = append(colNames, session.engine.Quote(expr.ColName)+"="+tp) case *builder.Builder: - subQuery, subArgs, err := session.statement.GenCondSQL(tp) + subQuery, subArgs, err := builder.ToSQL(tp) if err != nil { return 0, err } + subQuery = session.statement.ReplaceQuote(subQuery) colNames = append(colNames, session.engine.Quote(expr.ColName)+"=("+subQuery+")") args = append(args, subArgs...) default: @@ -279,7 +280,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 condBeanIsStruct := false if len(condiBean) > 0 { if c, ok := condiBean[0].(map[string]interface{}); ok { - var eq = make(builder.Eq) + eq := make(builder.Eq) for k, v := range c { eq[session.engine.Quote(k)] = v } @@ -323,11 +324,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 st := session.statement var ( - sqlStr string - condArgs []interface{} - condSQL string cond = session.statement.Conds().And(autoCond) - doIncVer = isStruct && (table != nil && table.Version != "" && session.statement.CheckVersion) verValue *reflect.Value ) @@ -347,70 +344,65 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 return 0, ErrNoColumnsTobeUpdated } - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + whereWriter := builder.NewWriter() + if cond.IsValid() { + fmt.Fprint(whereWriter, "WHERE ") + } + if err := cond.WriteTo(st.QuoteReplacer(whereWriter)); err != nil { + return 0, err + } + if err := st.WriteOrderBy(whereWriter); err != nil { return 0, err } - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } - - if st.OrderStr != "" { - condSQL += fmt.Sprintf(" ORDER BY %v", st.OrderStr) - } - - var tableName = session.statement.TableName() + tableName := session.statement.TableName() // TODO: Oracle support needed var top string if st.LimitN != nil { limitValue := *st.LimitN switch session.engine.dialect.URI().DBType { case schemas.MYSQL: - condSQL += fmt.Sprintf(" LIMIT %d", limitValue) + fmt.Fprintf(whereWriter, " LIMIT %d", limitValue) case schemas.SQLITE: - tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) + fmt.Fprintf(whereWriter, " LIMIT %d", limitValue) + cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", - session.engine.Quote(tableName), tempCondSQL), condArgs...)) - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + session.engine.Quote(tableName), whereWriter.String()), whereWriter.Args()...)) + + whereWriter = builder.NewWriter() + fmt.Fprint(whereWriter, "WHERE ") + if err := cond.WriteTo(st.QuoteReplacer(whereWriter)); err != nil { return 0, err } - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } case schemas.POSTGRES: - tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) + fmt.Fprintf(whereWriter, " LIMIT %d", limitValue) + cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", - session.engine.Quote(tableName), tempCondSQL), condArgs...)) - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + session.engine.Quote(tableName), whereWriter.String()), whereWriter.Args()...)) + + whereWriter = builder.NewWriter() + fmt.Fprint(whereWriter, "WHERE ") + if err := cond.WriteTo(st.QuoteReplacer(whereWriter)); err != nil { return 0, err } - - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } case schemas.MSSQL: - if st.OrderStr != "" && table != nil && len(table.PrimaryKeys) == 1 { + if st.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 { cond = builder.Expr(fmt.Sprintf("%s IN (SELECT TOP (%d) %s FROM %v%v)", table.PrimaryKeys[0], limitValue, table.PrimaryKeys[0], - session.engine.Quote(tableName), condSQL), condArgs...) + session.engine.Quote(tableName), whereWriter.String()), whereWriter.Args()...) - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + whereWriter = builder.NewWriter() + fmt.Fprint(whereWriter, "WHERE ") + if err := cond.WriteTo(whereWriter); err != nil { return 0, err } - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } } else { top = fmt.Sprintf("TOP (%d) ", limitValue) } } } - var tableAlias = session.engine.Quote(tableName) + tableAlias := session.engine.Quote(tableName) var fromSQL string if session.statement.TableAlias != "" { switch session.engine.dialect.URI().DBType { @@ -422,14 +414,19 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - sqlStr = fmt.Sprintf("UPDATE %v%v SET %v %v%v", + updateWriter := builder.NewWriter() + if _, err := fmt.Fprintf(updateWriter, "UPDATE %v%v SET %v %v", top, tableAlias, strings.Join(colNames, ", "), - fromSQL, - condSQL) + fromSQL); err != nil { + return 0, err + } + if err := utils.WriteBuilder(updateWriter, whereWriter); err != nil { + return 0, err + } - res, err := session.exec(sqlStr, append(args, condArgs...)...) + res, err := session.exec(updateWriter.String(), append(args, updateWriter.Args()...)...) if err != nil { return 0, err } else if doIncVer { @@ -535,7 +532,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac } args = append(args, val) - var colName = col.Name + colName := col.Name session.afterClosures = append(session.afterClosures, func(bean interface{}) { col := table.GetColumn(colName) setColumnTime(bean, col, t) From c98930f8f2a5827f54376b5e23467d29acca6ef5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 3 Jun 2022 15:24:24 +0800 Subject: [PATCH 164/179] Fix oid index for postgres (#2154) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2154 --- dialects/postgres.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 83e4187f..ba73aad7 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -862,11 +862,11 @@ func (db *postgres) needQuote(name string) bool { func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = postgresQuoter + q := postgresQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = postgresQuoter + q := postgresQuoter q.IsReserved = db.needQuote db.quoter = q case QuotePolicyAlways: @@ -1125,7 +1125,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A col.Name = strings.Trim(colName, `" `) if colDefault != nil { - var theDefault = *colDefault + theDefault := *colDefault // cockroach has type with the default value with ::: // and postgres with ::, we should remove them before store them idx := strings.Index(theDefault, ":::") @@ -1301,15 +1301,8 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN } colNames = getIndexColName(indexdef) - isSkip := false - //Oid It's a special index. You can't put it in - for _, element := range colNames { - if "oid" == element { - isSkip = true - break - } - } - if isSkip { + // Oid It's a special index. You can't put it in. TODO: This is not perfect. + if indexName == tableName+"_oid_index" && len(colNames) == 1 && colNames[0] == "oid" { continue } From f469d8816644fdb21ad3c6b6da85514daa23219f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 3 Jun 2022 17:44:49 +0800 Subject: [PATCH 165/179] Update changelog for 1.3.1 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fed4e261..ae213c99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.3.1](https://gitea.com/xorm/xorm/releases/tag/1.3.1) - 2022-06-03 + +* BREAKING + * Refactor orderby and support arguments (#2150) + * return a clear error for set TEXT type as compare condition (#2062) +* BUGFIXES + * Fix oid index for postgres (#2154) + * Add ORDER BY SEQ_IN_INDEX to MySQL GetIndexes to Fix IndexTests (#2152) + * some improvement (#2136) +* ENHANCEMENTS + * Add interface to allow structs to provide specific index information (#2137) + * MySQL/MariaDB: return max length for text columns (#2133) + * PostgreSQL: enable comment on column (#2131) +* TESTING + * Add test for find date (#2121) + ## [1.3.0](https://gitea.com/xorm/xorm/releases/tag/1.3.0) - 2022-04-14 * BREAKING From c3bce556200f3356803beec9147210ba46319f99 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 14 Jul 2022 13:55:24 +0800 Subject: [PATCH 166/179] Change schemas.Column to use int64 (#2160) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2160 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/dameng.go | 26 +++++++-------- dialects/mssql.go | 14 ++++---- dialects/mysql.go | 12 +++---- dialects/oracle.go | 20 +++++------ dialects/postgres.go | 10 +++--- dialects/time.go | 12 ++++--- integrations/engine_test.go | 8 ++--- schemas/column.go | 8 ++--- schemas/type.go | 4 +-- tags/tag.go | 66 ++++++++++++++++++------------------- 10 files changed, 89 insertions(+), 91 deletions(-) diff --git a/dialects/dameng.go b/dialects/dameng.go index f4a075d5..5e92ec2f 100644 --- a/dialects/dameng.go +++ b/dialects/dameng.go @@ -622,9 +622,9 @@ func (db *dameng) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -729,11 +729,11 @@ func (db *dameng) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl func (db *dameng) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = damengQuoter + q := damengQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = damengQuoter + q := damengQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -792,7 +792,7 @@ type dmClobObject interface { ReadString(int, int) (string, error) } -//var _ dmClobObject = &dm.DmClob{} +// var _ dmClobObject = &dm.DmClob{} func (d *dmClobScanner) Scan(data interface{}) error { if data == nil { @@ -927,7 +927,7 @@ func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableNam var ( ignore bool dt string - len1, len2 int + len1, len2 int64 ) dts := strings.Split(dataType.String, "(") @@ -935,10 +935,10 @@ func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableNam if len(dts) > 1 { lens := strings.Split(dts[1][:len(dts[1])-1], ",") if len(lens) > 1 { - len1, _ = strconv.Atoi(lens[0]) - len2, _ = strconv.Atoi(lens[1]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) + len2, _ = strconv.ParseInt(lens[1], 10, 64) } else { - len1, _ = strconv.Atoi(lens[0]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) } } @@ -972,9 +972,9 @@ func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableNam } if col.SQLType.Name == "TIMESTAMP" { - col.Length = int(dataScale.Int64) + col.Length = dataScale.Int64 } else { - col.Length = int(dataLen.Int64) + col.Length = dataLen.Int64 } if col.SQLType.IsTime() { @@ -1140,8 +1140,8 @@ func (d *damengDriver) GenScanResult(colType string) (interface{}, error) { } func (d *damengDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, vv ...interface{}) error { - var scanResults = make([]interface{}, 0, len(types)) - var replaces = make([]bool, 0, len(types)) + scanResults := make([]interface{}, 0, len(types)) + replaces := make([]bool, 0, len(types)) var err error for i, v := range vv { var replaced bool diff --git a/dialects/mssql.go b/dialects/mssql.go index 706a754a..1b6fe692 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -229,7 +229,7 @@ func (db *mssql) Init(uri *URI) error { func (db *mssql) SetParams(params map[string]string) { defaultVarchar, ok := params["DEFAULT_VARCHAR"] if ok { - var t = strings.ToUpper(defaultVarchar) + t := strings.ToUpper(defaultVarchar) switch t { case "NVARCHAR", "VARCHAR": db.defaultVarchar = t @@ -242,7 +242,7 @@ func (db *mssql) SetParams(params map[string]string) { defaultChar, ok := params["DEFAULT_CHAR"] if ok { - var t = strings.ToUpper(defaultChar) + t := strings.ToUpper(defaultChar) switch t { case "NCHAR", "CHAR": db.defaultChar = t @@ -375,9 +375,9 @@ func (db *mssql) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -403,11 +403,11 @@ func (db *mssql) IsReserved(name string) bool { func (db *mssql) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = mssqlQuoter + q := mssqlQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = mssqlQuoter + q := mssqlQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -475,7 +475,7 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName colSeq := make([]string, 0) for rows.Next() { var name, ctype, vdefault string - var maxLen, precision, scale int + var maxLen, precision, scale int64 var nullable, isPK, defaultIsNull, isIncrement bool err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &defaultIsNull, &vdefault, &isPK, &isIncrement) if err != nil { diff --git a/dialects/mysql.go b/dialects/mysql.go index 31e7b788..6ed4a1be 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -330,9 +330,9 @@ func (db *mysql) SQLType(c *schemas.Column) string { } if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } if isUnsigned { @@ -444,7 +444,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName // Remove the /* mariadb-5.3 */ suffix from coltypes colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */") colType = strings.ToUpper(colName) - var len1, len2 int + var len1, len2 int64 if len(cts) == 2 { idx := strings.Index(cts[1], ")") if colType == schemas.Enum && cts[1][0] == '\'' { // enum @@ -465,12 +465,12 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } } else { lens := strings.Split(cts[1][0:idx], ",") - len1, err = strconv.Atoi(strings.TrimSpace(lens[0])) + len1, err = strconv.ParseInt(strings.TrimSpace(lens[0]), 10, 64) if err != nil { return nil, nil, err } if len(lens) == 2 { - len2, err = strconv.Atoi(lens[1]) + len2, err = strconv.ParseInt(lens[1], 10, 64) if err != nil { return nil, nil, err } @@ -479,7 +479,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } else { switch colType { case "MEDIUMTEXT", "LONGTEXT", "TEXT": - len1, err = strconv.Atoi(*maxLength) + len1, err = strconv.ParseInt(*maxLength, 10, 64) if err != nil { return nil, nil, err } diff --git a/dialects/oracle.go b/dialects/oracle.go index 04652bd6..8328ff15 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -570,9 +570,9 @@ func (db *oracle) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -606,7 +606,7 @@ func (db *oracle) DropTableSQL(tableName string) (string, bool) { } func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { - var sql = "CREATE TABLE " + sql := "CREATE TABLE " if tableName == "" { tableName = table.Name } @@ -641,11 +641,11 @@ func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = oracleQuoter + q := oracleQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = oracleQuoter + q := oracleQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -690,7 +690,7 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam col.Indexes = make(map[string]int) var colName, colDefault, nullable, dataType, dataPrecision, dataScale *string - var dataLen int + var dataLen int64 err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision, &dataScale, &nullable) @@ -713,16 +713,16 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam var ignore bool var dt string - var len1, len2 int + var len1, len2 int64 dts := strings.Split(*dataType, "(") dt = dts[0] if len(dts) > 1 { lens := strings.Split(dts[1][:len(dts[1])-1], ",") if len(lens) > 1 { - len1, _ = strconv.Atoi(lens[0]) - len2, _ = strconv.Atoi(lens[1]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) + len2, _ = strconv.ParseInt(lens[1], 10, 64) } else { - len1, _ = strconv.Atoi(lens[0]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) } } diff --git a/dialects/postgres.go b/dialects/postgres.go index ba73aad7..3c7ecb35 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -934,9 +934,9 @@ func (db *postgres) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -1110,9 +1110,9 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A return nil, nil, err } - var maxLen int + var maxLen int64 if maxLenStr != nil { - maxLen, err = strconv.Atoi(*maxLenStr) + maxLen, err = strconv.ParseInt(*maxLenStr, 10, 64) if err != nil { return nil, nil, err } @@ -1186,7 +1186,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A startIdx := strings.Index(strings.ToLower(dataType), "string(") if startIdx != -1 && strings.HasSuffix(dataType, ")") { length := dataType[startIdx+8 : len(dataType)-1] - l, _ := strconv.Atoi(length) + l, _ := strconv.ParseInt(length, 10, 64) col.SQLType = schemas.SQLType{Name: "STRING", DefaultLength: l, DefaultLength2: 0} } else { col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} diff --git a/dialects/time.go b/dialects/time.go index f0bbb765..cdc896be 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -23,7 +23,7 @@ func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.C } } - var tmZone = dbLocation + tmZone := dbLocation if col.TimeZone != nil { tmZone = col.TimeZone } @@ -34,15 +34,17 @@ func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.C case schemas.Date: return t.Format("2006-01-02"), nil case schemas.Time: - var layout = "15:04:05" + layout := "15:04:05" if col.Length > 0 { - layout += "." + strings.Repeat("0", col.Length) + // we can use int(...) casting here as it's very unlikely to a huge sized field + layout += "." + strings.Repeat("0", int(col.Length)) } return t.Format(layout), nil case schemas.DateTime, schemas.TimeStamp: - var layout = "2006-01-02 15:04:05" + layout := "2006-01-02 15:04:05" if col.Length > 0 { - layout += "." + strings.Repeat("0", col.Length) + // we can use int(...) casting here as it's very unlikely to a huge sized field + layout += "." + strings.Repeat("0", int(col.Length)) } return t.Format(layout), nil case schemas.Varchar: diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 905c4f24..730a424e 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -290,13 +290,11 @@ func TestGetColumnsComment(t *testing.T) { } func TestGetColumnsLength(t *testing.T) { - var max_length int + var max_length int64 switch testEngine.Dialect().URI().DBType { - case - schemas.POSTGRES: + case schemas.POSTGRES: max_length = 0 - case - schemas.MYSQL: + case schemas.MYSQL: max_length = 65535 default: t.Skip() diff --git a/schemas/column.go b/schemas/column.go index 4bbb6c2d..001769cd 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -26,8 +26,8 @@ type Column struct { FieldIndex []int // Available only when parsed from a struct SQLType SQLType IsJSON bool - Length int - Length2 int + Length int64 + Length2 int64 Nullable bool Default string Indexes map[string]int @@ -48,7 +48,7 @@ type Column struct { } // NewColumn creates a new column -func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { +func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int64, nullable bool) *Column { return &Column{ Name: name, IsJSON: sqlType.IsJson(), @@ -82,7 +82,7 @@ func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { // ValueOfV returns column's filed of struct's value accept reflevt value func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { - var v = *dataStruct + v := *dataStruct for _, i := range col.FieldIndex { if v.Kind() == reflect.Ptr { if v.IsNil() { diff --git a/schemas/type.go b/schemas/type.go index 8702862a..b8b30851 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -28,8 +28,8 @@ const ( // SQLType represents SQL types type SQLType struct { Name string - DefaultLength int - DefaultLength2 int + DefaultLength int64 + DefaultLength2 int64 } // enumerates all columns types diff --git a/tags/tag.go b/tags/tag.go index 4e1f1ce7..55f5f4cf 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -99,33 +99,31 @@ type Context struct { // Handler describes tag handler for XORM type Handler func(ctx *Context) error -var ( - // defaultTagHandlers enumerates all the default tag handler - defaultTagHandlers = map[string]Handler{ - "-": IgnoreHandler, - "<-": OnlyFromDBTagHandler, - "->": OnlyToDBTagHandler, - "PK": PKTagHandler, - "NULL": NULLTagHandler, - "NOT": NotTagHandler, - "AUTOINCR": AutoIncrTagHandler, - "DEFAULT": DefaultTagHandler, - "CREATED": CreatedTagHandler, - "UPDATED": UpdatedTagHandler, - "DELETED": DeletedTagHandler, - "VERSION": VersionTagHandler, - "UTC": UTCTagHandler, - "LOCAL": LocalTagHandler, - "NOTNULL": NotNullTagHandler, - "INDEX": IndexTagHandler, - "UNIQUE": UniqueTagHandler, - "CACHE": CacheTagHandler, - "NOCACHE": NoCacheTagHandler, - "COMMENT": CommentTagHandler, - "EXTENDS": ExtendsTagHandler, - "UNSIGNED": UnsignedTagHandler, - } -) +// defaultTagHandlers enumerates all the default tag handler +var defaultTagHandlers = map[string]Handler{ + "-": IgnoreHandler, + "<-": OnlyFromDBTagHandler, + "->": OnlyToDBTagHandler, + "PK": PKTagHandler, + "NULL": NULLTagHandler, + "NOT": NotTagHandler, + "AUTOINCR": AutoIncrTagHandler, + "DEFAULT": DefaultTagHandler, + "CREATED": CreatedTagHandler, + "UPDATED": UpdatedTagHandler, + "DELETED": DeletedTagHandler, + "VERSION": VersionTagHandler, + "UTC": UTCTagHandler, + "LOCAL": LocalTagHandler, + "NOTNULL": NotNullTagHandler, + "INDEX": IndexTagHandler, + "UNIQUE": UniqueTagHandler, + "CACHE": CacheTagHandler, + "NOCACHE": NoCacheTagHandler, + "COMMENT": CommentTagHandler, + "EXTENDS": ExtendsTagHandler, + "UNSIGNED": UnsignedTagHandler, +} func init() { for k := range schemas.SqlTypes { @@ -312,16 +310,16 @@ func SQLTypeTagHandler(ctx *Context) error { default: var err error if len(ctx.params) == 2 { - ctx.col.Length, err = strconv.Atoi(ctx.params[0]) + ctx.col.Length, err = strconv.ParseInt(ctx.params[0], 10, 64) if err != nil { return err } - ctx.col.Length2, err = strconv.Atoi(ctx.params[1]) + ctx.col.Length2, err = strconv.ParseInt(ctx.params[1], 10, 64) if err != nil { return err } } else if len(ctx.params) == 1 { - ctx.col.Length, err = strconv.Atoi(ctx.params[0]) + ctx.col.Length, err = strconv.ParseInt(ctx.params[0], 10, 64) if err != nil { return err } @@ -332,8 +330,8 @@ func SQLTypeTagHandler(ctx *Context) error { // ExtendsTagHandler describes extends tag handler func ExtendsTagHandler(ctx *Context) error { - var fieldValue = ctx.fieldValue - var isPtr = false + fieldValue := ctx.fieldValue + isPtr := false switch fieldValue.Kind() { case reflect.Ptr: f := fieldValue.Type().Elem() @@ -355,7 +353,7 @@ func ExtendsTagHandler(ctx *Context) error { col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName) col.FieldIndex = append(ctx.col.FieldIndex, col.FieldIndex...) - var tagPrefix = ctx.col.FieldName + tagPrefix := ctx.col.FieldName if len(ctx.params) > 0 { col.Nullable = isPtr tagPrefix = strings.Trim(ctx.params[0], "'") @@ -378,7 +376,7 @@ func ExtendsTagHandler(ctx *Context) error { } } default: - //TODO: warning + // TODO: warning } return ErrIgnoreField } From c900ecc87fbd1e29a496a27d103adb07cfa9971f Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 3 Sep 2022 10:12:17 +0800 Subject: [PATCH 167/179] Prevent Sync failure with non-regular indexes on Postgres (#2174) When dropping indexes in Postgres if the index is non-regular we should not attempt to regularise the index name as it is already correct. Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/2174 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/postgres.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 3c7ecb35..f9de5859 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1030,11 +1030,10 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") tableName = tableParts[len(tableParts)-1] - if !strings.HasPrefix(idxName, "UQE_") && - !strings.HasPrefix(idxName, "IDX_") { - if index.Type == schemas.UniqueType { + if index.IsRegular { + if index.Type == schemas.UniqueType && !strings.HasPrefix(idxName, "UQE_") { idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) - } else { + } else if index.Type == schemas.IndexType && !strings.HasPrefix(idxName, "IDX_") { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } } From bd58520020dfb5bd6b7f5779e871d53aa9ee4c71 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 3 Sep 2022 15:14:19 +0800 Subject: [PATCH 168/179] add changelog for 1.3.2 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae213c99..6887cb97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.3.2](https://gitea.com/xorm/xorm/releases/tag/1.3.2) - 2022-09-03 + +* BUGFIXES + * Change schemas.Column to use int64 (#2160) +* MISC + * Prevent Sync failure with non-regular indexes on Postgres (#2174) + ## [1.3.1](https://gitea.com/xorm/xorm/releases/tag/1.3.1) - 2022-06-03 * BREAKING From 3acabdaf26fcf58fa51e9067235c0c1d456e66b5 Mon Sep 17 00:00:00 2001 From: stevefan1999 Date: Mon, 24 Oct 2022 11:29:54 +0800 Subject: [PATCH 169/179] Fix Oracle Table creation default value (#2190) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2190 Reviewed-by: Lunny Xiao Co-authored-by: stevefan1999 Co-committed-by: stevefan1999 --- dialects/oracle.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dialects/oracle.go b/dialects/oracle.go index 8328ff15..ce91cd5d 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -548,7 +548,14 @@ func (db *oracle) Features() *DialectFeatures { func (db *oracle) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Bool, schemas.Serial, schemas.BigSerial: + case schemas.Bool: + if c.Default == "true" { + c.Default = "1" + } else if c.Default == "false" { + c.Default = "0" + } + res = "NUMBER(1,0)" + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Serial, schemas.BigSerial: res = "NUMBER" case schemas.Binary, schemas.VarBinary, schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea: return schemas.Blob From 71a5939c65fa40906fa8bc5b194349e7cd018524 Mon Sep 17 00:00:00 2001 From: tylerthail2019 Date: Wed, 16 Nov 2022 13:22:04 +0800 Subject: [PATCH 170/179] add disable version check func (#2197) Co-authored-by: tyler Reviewed-on: https://gitea.com/xorm/xorm/pulls/2197 Reviewed-by: Lunny Xiao Co-authored-by: tylerthail2019 Co-committed-by: tylerthail2019 --- session.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/session.go b/session.go index 388678cd..854a125a 100644 --- a/session.go +++ b/session.go @@ -794,3 +794,9 @@ func (session *Session) PingContext(ctx context.Context) error { session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName()) return session.DB().PingContext(ctx) } + +// disable version check +func (session *Session) NoVersionCheck() *Session { + session.statement.CheckVersion = false + return session +} From f1bfc5ce983063d6262c125e68f59599541a2e6b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 9 Dec 2022 23:37:26 +0800 Subject: [PATCH 171/179] join support condition (#2201) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2201 --- engine.go | 16 ++++++++-------- integrations/session_find_test.go | 5 +++++ interface.go | 2 +- internal/statements/join.go | 30 ++++++++++++++++++++++++------ session.go | 2 +- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/engine.go b/engine.go index 81cfc7a9..a42519ee 100644 --- a/engine.go +++ b/engine.go @@ -330,7 +330,7 @@ func (engine *Engine) Ping() error { // SQL method let's you manually write raw SQL and operate // For example: // -// engine.SQL("select * from user").Find(&users) +// engine.SQL("select * from user").Find(&users) // // This code will execute "select * from user" and set the records to users func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session { @@ -996,9 +996,8 @@ func (engine *Engine) Desc(colNames ...string) *Session { // Asc will generate "ORDER BY column1,column2 Asc" // This method can chainable use. // -// engine.Desc("name").Asc("age").Find(&users) -// // SELECT * FROM user ORDER BY name DESC, age ASC -// +// engine.Desc("name").Asc("age").Find(&users) +// // SELECT * FROM user ORDER BY name DESC, age ASC func (engine *Engine) Asc(colNames ...string) *Session { session := engine.NewSession() session.isAutoClose = true @@ -1020,7 +1019,7 @@ func (engine *Engine) Prepare() *Session { } // Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { +func (engine *Engine) Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session { session := engine.NewSession() session.isAutoClose = true return session.Join(joinOperator, tablename, condition, args...) @@ -1220,9 +1219,10 @@ func (engine *Engine) InsertOne(bean interface{}) (int64, error) { // Update records, bean's non-empty fields are updated contents, // condiBean' non-empty filds are conditions // CAUTION: -// 1.bool will defaultly be updated content nor conditions -// You should call UseBool if you have bool to use. -// 2.float32 & float64 may be not inexact as conditions +// +// 1.bool will defaultly be updated content nor conditions +// You should call UseBool if you have bool to use. +// 2.float32 & float64 may be not inexact as conditions func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64, error) { session := engine.NewSession() defer session.Close() diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 6701b1b5..5c2a4c68 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "xorm.io/builder" "xorm.io/xorm" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" @@ -965,6 +966,10 @@ func TestFindJoin(t *testing.T) { scenes = make([]SceneItem, 0) err = testEngine.Join("INNER", "order", "`scene_item`.`device_id`=`order`.`id`").Find(&scenes) assert.NoError(t, err) + + scenes = make([]SceneItem, 0) + err = testEngine.Join("INNER", "order", builder.Expr("`scene_item`.`device_id`=`order`.`id`")).Find(&scenes) + assert.NoError(t, err) } func TestJoinFindLimit(t *testing.T) { diff --git a/interface.go b/interface.go index 55ffebe4..6ad0577a 100644 --- a/interface.go +++ b/interface.go @@ -52,7 +52,7 @@ type Interface interface { NoAutoCondition(...bool) *Session NotIn(string, ...interface{}) *Session Nullable(...string) *Session - Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session + Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session Omit(columns ...string) *Session OrderBy(order interface{}, args ...interface{}) *Session Ping() error diff --git a/internal/statements/join.go b/internal/statements/join.go index 45fc2441..adf349e7 100644 --- a/internal/statements/join.go +++ b/internal/statements/join.go @@ -15,7 +15,7 @@ import ( ) // Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { +func (statement *Statement) Join(joinOP string, tablename interface{}, condition interface{}, args ...interface{}) *Statement { var buf strings.Builder if len(statement.JoinStr) > 0 { fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) @@ -23,6 +23,23 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition fmt.Fprintf(&buf, "%v JOIN ", joinOP) } + condStr := "" + condArgs := []interface{}{} + switch condTp := condition.(type) { + case string: + condStr = condTp + case builder.Cond: + var err error + condStr, condArgs, err = builder.ToSQL(condTp) + if err != nil { + statement.LastError = err + return statement + } + default: + statement.LastError = fmt.Errorf("unsupported join condition type: %v", condTp) + return statement + } + switch tp := tablename.(type) { case builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() @@ -35,8 +52,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) aliasName = schemas.CommonQuoter.Trim(aliasName) - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condStr)) + statement.joinArgs = append(append(statement.joinArgs, subQueryArgs...), condArgs...) case *builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() if err != nil { @@ -48,8 +65,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) aliasName = schemas.CommonQuoter.Trim(aliasName) - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condStr)) + statement.joinArgs = append(append(statement.joinArgs, subQueryArgs...), condArgs...) default: tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) if !utils.IsSubQuery(tbName) { @@ -59,7 +76,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition } else { tbName = statement.ReplaceQuote(tbName) } - fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) + fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condStr)) + statement.joinArgs = append(statement.joinArgs, condArgs...) } statement.JoinStr = buf.String() diff --git a/session.go b/session.go index 854a125a..e1a16e5b 100644 --- a/session.go +++ b/session.go @@ -330,7 +330,7 @@ func (session *Session) NoCache() *Session { } // Join join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (session *Session) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { +func (session *Session) Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session { session.statement.Join(joinOperator, tablename, condition, args...) return session } From 71270edfcc768f6344cd4b408f7a27c071197d85 Mon Sep 17 00:00:00 2001 From: Dmitry Narizhnykh Date: Mon, 12 Dec 2022 18:35:40 +0800 Subject: [PATCH 172/179] add delimiters between COMMENT ON COLUMN... for Postgres (#2156) Fix bug: CreateTableSQL func generates invalid SQL without ";" delimiters. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2156 Reviewed-by: Lunny Xiao Co-authored-by: Dmitry Narizhnykh Co-committed-by: Dmitry Narizhnykh --- dialects/postgres.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index f9de5859..5efe54f4 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1343,14 +1343,14 @@ func (db *postgres) CreateTableSQL(ctx context.Context, queryer core.Queryer, ta commentSQL := "; " if table.Comment != "" { // support schema.table -> "schema"."table" - commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'", quoter.Quote(tableName), table.Comment) + commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'; ", quoter.Quote(tableName), table.Comment) } for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if len(col.Comment) > 0 { - commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'; ", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) } } From 5fafa00043917e1253ebac95b5de29e2f08a569f Mon Sep 17 00:00:00 2001 From: tamalsaha Date: Mon, 9 Jan 2023 13:19:29 +0800 Subject: [PATCH 173/179] fix: Correctly parse jsonb column tag (#2206) Signed-off-by: Tamal Saha Co-authored-by: Tamal Saha Reviewed-on: https://gitea.com/xorm/xorm/pulls/2206 Reviewed-by: Lunny Xiao Co-authored-by: tamalsaha Co-committed-by: tamalsaha --- tags/parser_test.go | 23 +++++++++++++++++++++++ tags/tag.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tags/parser_test.go b/tags/parser_test.go index 83c81a1e..434cfc07 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -557,6 +557,29 @@ func TestParseWithJSON(t *testing.T) { assert.True(t, table.Columns()[0].IsJSON) } +func TestParseWithJSONB(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("postgres"), + names.GonicMapper{ + "JSONB": true, + }, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithJSONB struct { + Default1 []string `db:"jsonb"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithJSONB))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_jsonb", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "default1", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].IsJSON) +} + func TestParseWithSQLType(t *testing.T) { parser := NewParser( "db", diff --git a/tags/tag.go b/tags/tag.go index 55f5f4cf..41d525e1 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -285,7 +285,7 @@ func CommentTagHandler(ctx *Context) error { // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *Context) error { ctx.col.SQLType = schemas.SQLType{Name: ctx.tagUname} - if ctx.tagUname == "JSON" { + if ctx.tagUname == "JSON" || ctx.tagUname == "JSONB" { ctx.col.IsJSON = true } if len(ctx.params) == 0 { From 7dc2a188761ac198bb424b98aa1fc22892798684 Mon Sep 17 00:00:00 2001 From: datbeohbbh Date: Fri, 3 Feb 2023 17:24:16 +0800 Subject: [PATCH 174/179] Bug fix: `Rows` must be closed after used (#2214) Issue: [#2213](https://gitea.com/xorm/xorm/issues/2213#issue-132724) Co-authored-by: datbeohbbh Reviewed-on: https://gitea.com/xorm/xorm/pulls/2214 Reviewed-by: Lunny Xiao Co-authored-by: datbeohbbh Co-committed-by: datbeohbbh --- engine.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine.go b/engine.go index a42519ee..f10e30d3 100644 --- a/engine.go +++ b/engine.go @@ -815,6 +815,9 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w return err } } + // !datbeohbbh! if no error, manually close + rows.Close() + sess.Close() } return nil } From 0c9963c6379477764ab4adbab74195f92d3b89dc Mon Sep 17 00:00:00 2001 From: jamlacey Date: Sat, 4 Feb 2023 21:24:29 +0800 Subject: [PATCH 175/179] Add support for go-ora driver (#2215) Co-authored-by: James Lacey Reviewed-on: https://gitea.com/xorm/xorm/pulls/2215 Reviewed-by: Lunny Xiao Co-authored-by: jamlacey Co-committed-by: jamlacey --- README.md | 1 + dialects/dialect.go | 1 + dialects/oracle.go | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/README.md b/README.md index ccf49348..f30449a1 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Drivers for Go's sql package which currently support database/sql includes: * Oracle - [github.com/godror/godror](https://github.com/godror/godror) (experiment) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) + - [github.com/sijms/go-ora](https://github.com/sijms/go-ora) (experiment) ## Installation diff --git a/dialects/dialect.go b/dialects/dialect.go index 555d96c6..70d599e6 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -290,6 +290,7 @@ func regDrvsNDialects() bool { "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, + "oracle": {"oracle", func() Driver { return &oracleDriver{} }, func() Dialect { return &oracle{} }}, } for driverName, v := range providedDrvsNDialects { diff --git a/dialects/oracle.go b/dialects/oracle.go index ce91cd5d..72c26ce2 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -939,3 +939,7 @@ func (o *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { } return db, nil } + +type oracleDriver struct { + godrorDriver +} From 52855dae32d7896bd0a12007d153168b77bb52db Mon Sep 17 00:00:00 2001 From: datbeohbbh Date: Sun, 12 Feb 2023 11:16:53 +0800 Subject: [PATCH 176/179] update go version to v1.17 in .drone.yml (#2219) issue: #2218 Co-authored-by: datbeohbbh Reviewed-on: https://gitea.com/xorm/xorm/pulls/2219 Reviewed-by: Lunny Xiao Co-authored-by: datbeohbbh Co-committed-by: datbeohbbh --- .drone.yml | 32 ++-- doc.go | 251 ++++++++++++++-------------- integrations/engine_dm_test.go | 1 + integrations/session_insert_test.go | 3 +- internal/json/gojson.go | 1 + internal/json/jsoniter.go | 1 + schemas/quote.go | 23 +-- session_update.go | 7 +- 8 files changed, 162 insertions(+), 157 deletions(-) diff --git a/.drone.yml b/.drone.yml index 210572b0..2bad4b5a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ trigger: - refs/pull/*/head steps: - name: test-vet - image: golang:1.15 + image: golang:1.17 pull: always volumes: - name: cache @@ -19,7 +19,7 @@ steps: commands: - make vet - name: test-sqlite3 - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -31,7 +31,7 @@ steps: - make test-sqlite3 - TEST_CACHE_ENABLE=true make test-sqlite3 - name: test-sqlite - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -41,7 +41,7 @@ steps: - make test-sqlite - TEST_QUOTE_POLICY=reserved make test-sqlite - name: test-mysql - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -58,7 +58,7 @@ steps: - TEST_CACHE_ENABLE=true make test-mysql - name: test-mysql-utf8mb4 - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -98,7 +98,7 @@ trigger: - refs/pull/*/head steps: - name: test-mysql8 - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -136,7 +136,7 @@ trigger: - refs/pull/*/head steps: - name: test-mariadb - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -175,7 +175,7 @@ trigger: steps: - name: test-postgres pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -190,7 +190,7 @@ steps: - name: test-postgres-schema pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -207,7 +207,7 @@ steps: - name: test-pgx pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -225,7 +225,7 @@ steps: - name: test-pgx-schema pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -267,7 +267,7 @@ trigger: steps: - name: test-mssql pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -306,7 +306,7 @@ trigger: steps: - name: test-tidb pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -339,7 +339,7 @@ trigger: steps: - name: test-cockroach pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -375,7 +375,7 @@ services: # steps: # - name: test-dameng # pull: never -# image: golang:1.15 +# image: golang:1.17 # volumes: # - name: cache # path: /go/pkg/mod @@ -416,7 +416,7 @@ trigger: - refs/pull/*/head steps: - name: merge_coverage - image: golang:1.15 + image: golang:1.17 commands: - make coverage diff --git a/doc.go b/doc.go index a1565806..f88f5371 100644 --- a/doc.go +++ b/doc.go @@ -3,247 +3,246 @@ // license that can be found in the LICENSE file. /* - Package xorm is a simple and powerful ORM for Go. -Installation +# Installation Make sure you have installed Go 1.11+ and then: - go get xorm.io/xorm + go get xorm.io/xorm -Create Engine +# Create Engine Firstly, we should create an engine for a database - engine, err := xorm.NewEngine(driverName, dataSourceName) + engine, err := xorm.NewEngine(driverName, dataSourceName) Method NewEngine's parameters are the same as sql.Open which depend drivers' implementation. Generally, one engine for an application is enough. You can define it as a package variable. -Raw Methods +# Raw Methods XORM supports raw SQL execution: 1. query with a SQL string, the returned results is []map[string][]byte - results, err := engine.Query("select * from user") + results, err := engine.Query("select * from user") 2. query with a SQL string, the returned results is []map[string]string - results, err := engine.QueryString("select * from user") + results, err := engine.QueryString("select * from user") 3. query with a SQL string, the returned results is []map[string]interface{} - results, err := engine.QueryInterface("select * from user") + results, err := engine.QueryInterface("select * from user") 4. execute with a SQL string, the returned results - affected, err := engine.Exec("update user set .... where ...") + affected, err := engine.Exec("update user set .... where ...") -ORM Methods +# ORM Methods There are 8 major ORM methods and many helpful methods to use to operate database. 1. Insert one or multiple records to database - affected, err := engine.Insert(&struct) - // INSERT INTO struct () values () - affected, err := engine.Insert(&struct1, &struct2) - // INSERT INTO struct1 () values () - // INSERT INTO struct2 () values () - affected, err := engine.Insert(&sliceOfStruct) - // INSERT INTO struct () values (),(),() - affected, err := engine.Insert(&struct1, &sliceOfStruct2) - // INSERT INTO struct1 () values () - // INSERT INTO struct2 () values (),(),() + affected, err := engine.Insert(&struct) + // INSERT INTO struct () values () + affected, err := engine.Insert(&struct1, &struct2) + // INSERT INTO struct1 () values () + // INSERT INTO struct2 () values () + affected, err := engine.Insert(&sliceOfStruct) + // INSERT INTO struct () values (),(),() + affected, err := engine.Insert(&struct1, &sliceOfStruct2) + // INSERT INTO struct1 () values () + // INSERT INTO struct2 () values (),(),() 2. Query one record or one variable from database - has, err := engine.Get(&user) - // SELECT * FROM user LIMIT 1 + has, err := engine.Get(&user) + // SELECT * FROM user LIMIT 1 - var id int64 - has, err := engine.Table("user").Where("name = ?", name).Get(&id) - // SELECT id FROM user WHERE name = ? LIMIT 1 + var id int64 + has, err := engine.Table("user").Where("name = ?", name).Get(&id) + // SELECT id FROM user WHERE name = ? LIMIT 1 - var id int64 - var name string - has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) - // SELECT id, name FROM user LIMIT 1 + var id int64 + var name string + has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) + // SELECT id, name FROM user LIMIT 1 3. Query multiple records from database - var sliceOfStructs []Struct - err := engine.Find(&sliceOfStructs) - // SELECT * FROM user + var sliceOfStructs []Struct + err := engine.Find(&sliceOfStructs) + // SELECT * FROM user - var mapOfStructs = make(map[int64]Struct) - err := engine.Find(&mapOfStructs) - // SELECT * FROM user + var mapOfStructs = make(map[int64]Struct) + err := engine.Find(&mapOfStructs) + // SELECT * FROM user - var int64s []int64 - err := engine.Table("user").Cols("id").Find(&int64s) - // SELECT id FROM user + var int64s []int64 + err := engine.Table("user").Cols("id").Find(&int64s) + // SELECT id FROM user 4. Query multiple records and record by record handle, there two methods, one is Iterate, another is Rows - err := engine.Iterate(new(User), func(i int, bean interface{}) error { - // do something - }) - // SELECT * FROM user + err := engine.Iterate(new(User), func(i int, bean interface{}) error { + // do something + }) + // SELECT * FROM user - rows, err := engine.Rows(...) - // SELECT * FROM user - defer rows.Close() - bean := new(Struct) - for rows.Next() { - err = rows.Scan(bean) - } + rows, err := engine.Rows(...) + // SELECT * FROM user + defer rows.Close() + bean := new(Struct) + for rows.Next() { + err = rows.Scan(bean) + } or - rows, err := engine.Cols("name", "age").Rows(...) - // SELECT * FROM user - defer rows.Close() - for rows.Next() { - var name string - var age int - err = rows.Scan(&name, &age) - } + rows, err := engine.Cols("name", "age").Rows(...) + // SELECT * FROM user + defer rows.Close() + for rows.Next() { + var name string + var age int + err = rows.Scan(&name, &age) + } 5. Update one or more records - affected, err := engine.ID(...).Update(&user) - // UPDATE user SET ... + affected, err := engine.ID(...).Update(&user) + // UPDATE user SET ... 6. Delete one or more records, Delete MUST has condition - affected, err := engine.Where(...).Delete(&user) - // DELETE FROM user Where ... + affected, err := engine.Where(...).Delete(&user) + // DELETE FROM user Where ... 7. Count records - counts, err := engine.Count(&user) - // SELECT count(*) AS total FROM user + counts, err := engine.Count(&user) + // SELECT count(*) AS total FROM user - counts, err := engine.SQL("select count(*) FROM user").Count() - // select count(*) FROM user + counts, err := engine.SQL("select count(*) FROM user").Count() + // select count(*) FROM user 8. Sum records - sumFloat64, err := engine.Sum(&user, "id") - // SELECT sum(id) from user + sumFloat64, err := engine.Sum(&user, "id") + // SELECT sum(id) from user - sumFloat64s, err := engine.Sums(&user, "id1", "id2") - // SELECT sum(id1), sum(id2) from user + sumFloat64s, err := engine.Sums(&user, "id1", "id2") + // SELECT sum(id1), sum(id2) from user - sumInt64s, err := engine.SumsInt(&user, "id1", "id2") - // SELECT sum(id1), sum(id2) from user + sumInt64s, err := engine.SumsInt(&user, "id1", "id2") + // SELECT sum(id1), sum(id2) from user -Conditions +# Conditions The above 8 methods could use with condition methods chainable. Notice: the above 8 methods should be the last chainable method. 1. ID, In - engine.ID(1).Get(&user) // for single primary key - // SELECT * FROM user WHERE id = 1 - engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys - // SELECT * FROM user WHERE id1 = 1 AND id2 = 2 - engine.In("id", 1, 2, 3).Find(&users) - // SELECT * FROM user WHERE id IN (1, 2, 3) - engine.In("id", []int{1, 2, 3}).Find(&users) - // SELECT * FROM user WHERE id IN (1, 2, 3) + engine.ID(1).Get(&user) // for single primary key + // SELECT * FROM user WHERE id = 1 + engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys + // SELECT * FROM user WHERE id1 = 1 AND id2 = 2 + engine.In("id", 1, 2, 3).Find(&users) + // SELECT * FROM user WHERE id IN (1, 2, 3) + engine.In("id", []int{1, 2, 3}).Find(&users) + // SELECT * FROM user WHERE id IN (1, 2, 3) 2. Where, And, Or - engine.Where().And().Or().Find() - // SELECT * FROM user WHERE (.. AND ..) OR ... + engine.Where().And().Or().Find() + // SELECT * FROM user WHERE (.. AND ..) OR ... 3. OrderBy, Asc, Desc - engine.Asc().Desc().Find() - // SELECT * FROM user ORDER BY .. ASC, .. DESC - engine.OrderBy().Find() - // SELECT * FROM user ORDER BY .. + engine.Asc().Desc().Find() + // SELECT * FROM user ORDER BY .. ASC, .. DESC + engine.OrderBy().Find() + // SELECT * FROM user ORDER BY .. 4. Limit, Top - engine.Limit().Find() - // SELECT * FROM user LIMIT .. OFFSET .. - engine.Top(5).Find() - // SELECT TOP 5 * FROM user // for mssql - // SELECT * FROM user LIMIT .. OFFSET 0 //for other databases + engine.Limit().Find() + // SELECT * FROM user LIMIT .. OFFSET .. + engine.Top(5).Find() + // SELECT TOP 5 * FROM user // for mssql + // SELECT * FROM user LIMIT .. OFFSET 0 //for other databases 5. SQL, let you custom SQL - var users []User - engine.SQL("select * from user").Find(&users) + var users []User + engine.SQL("select * from user").Find(&users) 6. Cols, Omit, Distinct - var users []*User - engine.Cols("col1, col2").Find(&users) - // SELECT col1, col2 FROM user - engine.Cols("col1", "col2").Where().Update(user) - // UPDATE user set col1 = ?, col2 = ? Where ... - engine.Omit("col1").Find(&users) - // SELECT col2, col3 FROM user - engine.Omit("col1").Insert(&user) - // INSERT INTO table (non-col1) VALUES () - engine.Distinct("col1").Find(&users) - // SELECT DISTINCT col1 FROM user + var users []*User + engine.Cols("col1, col2").Find(&users) + // SELECT col1, col2 FROM user + engine.Cols("col1", "col2").Where().Update(user) + // UPDATE user set col1 = ?, col2 = ? Where ... + engine.Omit("col1").Find(&users) + // SELECT col2, col3 FROM user + engine.Omit("col1").Insert(&user) + // INSERT INTO table (non-col1) VALUES () + engine.Distinct("col1").Find(&users) + // SELECT DISTINCT col1 FROM user 7. Join, GroupBy, Having - engine.GroupBy("name").Having("name='xlw'").Find(&users) - //SELECT * FROM user GROUP BY name HAVING name='xlw' - engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users) - //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id + engine.GroupBy("name").Having("name='xlw'").Find(&users) + //SELECT * FROM user GROUP BY name HAVING name='xlw' + engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users) + //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id -Builder +# Builder xorm could work with xorm.io/builder directly. 1. With Where - var cond = builder.Eq{"a":1, "b":2} - engine.Where(cond).Find(&users) + var cond = builder.Eq{"a":1, "b":2} + engine.Where(cond).Find(&users) 2. With In - var subQuery = builder.Select("name").From("group") - engine.In("group_name", subQuery).Find(&users) + var subQuery = builder.Select("name").From("group") + engine.In("group_name", subQuery).Find(&users) 3. With Join - var subQuery = builder.Select("name").From("group") - engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users) + var subQuery = builder.Select("name").From("group") + engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users) 4. With SetExprs - var subQuery = builder.Select("name").From("group") - engine.ID(1).SetExprs("name", subQuery).Update(new(User)) + var subQuery = builder.Select("name").From("group") + engine.ID(1).SetExprs("name", subQuery).Update(new(User)) 5. With SQL - var query = builder.Select("name").From("group") - results, err := engine.SQL(query).Find(&groups) + var query = builder.Select("name").From("group") + results, err := engine.SQL(query).Find(&groups) 6. With Query - var query = builder.Select("name").From("group") - results, err := engine.Query(query) - results, err := engine.QueryString(query) - results, err := engine.QueryInterface(query) + var query = builder.Select("name").From("group") + results, err := engine.Query(query) + results, err := engine.QueryString(query) + results, err := engine.QueryInterface(query) 7. With Exec - var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2") - results, err := engine.Exec(query) + var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2") + results, err := engine.Exec(query) More usage, please visit http://xorm.io/docs */ diff --git a/integrations/engine_dm_test.go b/integrations/engine_dm_test.go index 6c2f6103..3b195ef8 100644 --- a/integrations/engine_dm_test.go +++ b/integrations/engine_dm_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dm // +build dm package integrations diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 2495c1df..084deb38 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -744,7 +744,8 @@ func TestInsertMap(t *testing.T) { assert.EqualValues(t, "lunny", ims[3].Name) } -/*INSERT INTO `issue` (`repo_id`, `poster_id`, ... ,`name`, `content`, ... ,`index`) +/* +INSERT INTO `issue` (`repo_id`, `poster_id`, ... ,`name`, `content`, ... ,`index`) SELECT $1, $2, ..., $14, $15, ..., MAX(`index`) + 1 FROM `issue` WHERE `repo_id` = $1; */ func TestInsertWhere(t *testing.T) { diff --git a/internal/json/gojson.go b/internal/json/gojson.go index 4f1448e7..9bfa5c29 100644 --- a/internal/json/gojson.go +++ b/internal/json/gojson.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build gojson // +build gojson package json diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index cfe7a19e..be93ac4e 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build jsoniter // +build jsoniter package json diff --git a/schemas/quote.go b/schemas/quote.go index 4cab30fe..6df7bf0b 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -163,17 +163,18 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error { } // QuoteTo quotes the table or column names. i.e. if the quotes are [ and ] -// name -> [name] -// `name` -> [name] -// [name] -> [name] -// schema.name -> [schema].[name] -// `schema`.`name` -> [schema].[name] -// `schema`.name -> [schema].[name] -// schema.`name` -> [schema].[name] -// [schema].name -> [schema].[name] -// schema.[name] -> [schema].[name] -// name AS a -> [name] AS a -// schema.name AS a -> [schema].[name] AS a +// +// name -> [name] +// `name` -> [name] +// [name] -> [name] +// schema.name -> [schema].[name] +// `schema`.`name` -> [schema].[name] +// `schema`.name -> [schema].[name] +// schema.`name` -> [schema].[name] +// [schema].name -> [schema].[name] +// schema.[name] -> [schema].[name] +// name AS a -> [name] AS a +// schema.name AS a -> [schema].[name] AS a func (q Quoter) QuoteTo(buf *strings.Builder, value string) error { var i int for i < len(value) { diff --git a/session_update.go b/session_update.go index 76f311d6..e7104710 100644 --- a/session_update.go +++ b/session_update.go @@ -145,9 +145,10 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri // Update records, bean's non-empty fields are updated contents, // condiBean' non-empty filds are conditions // CAUTION: -// 1.bool will defaultly be updated content nor conditions -// You should call UseBool if you have bool to use. -// 2.float32 & float64 may be not inexact as conditions +// +// 1.bool will defaultly be updated content nor conditions +// You should call UseBool if you have bool to use. +// 2.float32 & float64 may be not inexact as conditions func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) { if session.isAutoClose { defer session.Close() From 056cecc97e9ef3f5fd216944a495c41aa98f4e4a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 20 Feb 2023 07:17:35 +0800 Subject: [PATCH 177/179] Add `Truncate` method (#2220) This PR adds a `Truncate` method which allows to delete all existing rows in a table. The current `Delete` implementation enforces conditions to prevent accidental data deletion. Co-authored-by: KN4CK3R Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2220 Reviewed-by: Lunny Xiao Co-authored-by: KN4CK3R Co-committed-by: KN4CK3R --- engine.go | 9 +++++++++ integrations/session_delete_test.go | 27 ++++++++++++++++++++++++++- interface.go | 1 + session_delete.go | 13 ++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/engine.go b/engine.go index f10e30d3..fb19176e 100644 --- a/engine.go +++ b/engine.go @@ -1233,12 +1233,21 @@ func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64 } // Delete records, bean's non-empty fields are conditions +// At least one condition must be set. func (engine *Engine) Delete(beans ...interface{}) (int64, error) { session := engine.NewSession() defer session.Close() return session.Delete(beans...) } +// Truncate records, bean's non-empty fields are conditions +// In contrast to Delete this method allows deletes without conditions. +func (engine *Engine) Truncate(beans ...interface{}) (int64, error) { + session := engine.NewSession() + defer session.Close() + return session.Truncate(beans...) +} + // Get retrieve one record from table, bean's non-empty fields // are conditions func (engine *Engine) Get(beans ...interface{}) (bool, error) { diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index b4e40edb..680c3215 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -208,7 +208,7 @@ func TestUnscopeDelete(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - var nowUnix = time.Now().Unix() + nowUnix := time.Now().Unix() var s UnscopeDeleteStruct cnt, err = testEngine.ID(1).Delete(&s) assert.NoError(t, err) @@ -266,3 +266,28 @@ func TestDelete2(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func TestTruncate(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type TruncateUser struct { + Uid int64 `xorm:"id pk not null autoincr"` + } + + assert.NoError(t, testEngine.Sync(new(TruncateUser))) + + cnt, err := testEngine.Insert(&TruncateUser{}) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + _, err = testEngine.Delete(&TruncateUser{}) + assert.Error(t, err) + + _, err = testEngine.Truncate(&TruncateUser{}) + assert.NoError(t, err) + + user2 := TruncateUser{} + has, err := testEngine.ID(1).Get(&user2) + assert.NoError(t, err) + assert.False(t, has) +} diff --git a/interface.go b/interface.go index 6ad0577a..d10abe9e 100644 --- a/interface.go +++ b/interface.go @@ -31,6 +31,7 @@ type Interface interface { Decr(column string, arg ...interface{}) *Session Desc(...string) *Session Delete(...interface{}) (int64, error) + Truncate(...interface{}) (int64, error) Distinct(columns ...string) *Session DropIndexes(bean interface{}) error Exec(sqlOrArgs ...interface{}) (sql.Result, error) diff --git a/session_delete.go b/session_delete.go index 322d5a44..d36b9e52 100644 --- a/session_delete.go +++ b/session_delete.go @@ -91,7 +91,18 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri } // Delete records, bean's non-empty fields are conditions +// At least one condition must be set. func (session *Session) Delete(beans ...interface{}) (int64, error) { + return session.delete(beans, true) +} + +// Truncate records, bean's non-empty fields are conditions +// In contrast to Delete this method allows deletes without conditions. +func (session *Session) Truncate(beans ...interface{}) (int64, error) { + return session.delete(beans, false) +} + +func (session *Session) delete(beans []interface{}, mustHaveConditions bool) (int64, error) { if session.isAutoClose { defer session.Close() } @@ -127,7 +138,7 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } pLimitN := session.statement.LimitN - if condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { + if mustHaveConditions && condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { return 0, ErrNeedDeletedCond } From 914f2db9eaf24184a34499e8ba8a705202a49c65 Mon Sep 17 00:00:00 2001 From: fanshengshuai Date: Tue, 28 Feb 2023 23:42:42 +0800 Subject: [PATCH 178/179] =?UTF-8?q?mysql=E5=AD=97=E6=AE=B5=E4=B8=BAUNSIGNE?= =?UTF-8?q?D=E6=97=B6=EF=BC=8C=E4=BC=9A=E5=AF=BC=E8=87=B4=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=B1=BB=E5=9E=8B=E6=97=A0=E6=B3=95=E8=AF=86=E5=88=AB?= =?UTF-8?q?=EF=BC=8C=E8=BF=94=E5=9B=9ERawBytes=EF=BC=8CJSON=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=98=A0=E5=B0=84=E4=B8=BAString=20(#2225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 如题 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2225 Reviewed-by: Lunny Xiao Co-authored-by: fanshengshuai Co-committed-by: fanshengshuai --- dialects/mysql.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dialects/mysql.go b/dialects/mysql.go index 6ed4a1be..195e1f23 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -738,8 +738,9 @@ func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { } func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) { + colType = strings.Replace(colType, "UNSIGNED ", "", -1) switch colType { - case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET": + case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET", "JSON": var s sql.NullString return &s, nil case "BIGINT": From d485abba57930d6eacd607405a84c6862886102f Mon Sep 17 00:00:00 2001 From: datbeohbbh Date: Mon, 6 Mar 2023 18:55:33 +0800 Subject: [PATCH 179/179] add `(*Engine) SetConnMaxIdleTime` (#2229) issue: https://gitea.com/xorm/xorm/issues/2228 Co-authored-by: datbeohbbh Reviewed-on: https://gitea.com/xorm/xorm/pulls/2229 Reviewed-by: Lunny Xiao Co-authored-by: datbeohbbh Co-committed-by: datbeohbbh --- engine.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine.go b/engine.go index fb19176e..389819e7 100644 --- a/engine.go +++ b/engine.go @@ -254,6 +254,11 @@ func (engine *Engine) SetConnMaxLifetime(d time.Duration) { engine.DB().SetConnMaxLifetime(d) } +// SetConnMaxIdleTime sets the maximum amount of time a connection may be idle. +func (engine *Engine) SetConnMaxIdleTime(d time.Duration) { + engine.DB().SetConnMaxIdleTime(d) +} + // SetMaxOpenConns is only available for go 1.2+ func (engine *Engine) SetMaxOpenConns(conns int) { engine.DB().SetMaxOpenConns(conns)