From e6295a7c61649a1abeb3fdfc934a5a0c9ac611fe Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 19 Jan 2020 16:21:53 +0800 Subject: [PATCH 001/112] remove go1.10 test on drone --- .drone.yml | 216 ++++------------------------------------------------- 1 file changed, 13 insertions(+), 203 deletions(-) diff --git a/.drone.yml b/.drone.yml index b2198e38..d9d0c94e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,205 +1,15 @@ --- kind: pipeline -name: go1.10-test -workspace: - base: /go - path: src/gitea.com/xorm/xorm - +name: testing steps: -- name: build +- name: vet pull: default - image: golang:1.10 - commands: - - go get -t -d -v - - go build -v - when: - event: - - push - - pull_request - -- name: test-sqlite - pull: default - image: golang:1.10 - depends_on: - - build - commands: - - "go test -v -race -db=\"sqlite3\" -conn_str=\"./test.db\" -coverprofile=coverage1-1.txt -covermode=atomic" - - "go test -v -race -db=\"sqlite3\" -conn_str=\"./test.db\" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-mysql - pull: default - image: golang:1.10 - depends_on: - - build - commands: - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test\" -coverprofile=coverage2-1.txt -covermode=atomic" - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test\" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-mysql-utf8mb4 - pull: default - image: golang:1.10 - depends_on: - - test-mysql - commands: - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test?charset=utf8mb4\" -coverprofile=coverage2.1-1.txt -covermode=atomic" - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test?charset=utf8mb4\" -cache=true -coverprofile=coverage2.1-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-mymysql - pull: default - image: golang:1.10 - depends_on: - - test-mysql-utf8mb4 - commands: - - "go test -v -race -db=\"mymysql\" -conn_str=\"tcp:mysql:3306*xorm_test/root/\" -coverprofile=coverage3-1.txt -covermode=atomic" - - "go test -v -race -db=\"mymysql\" -conn_str=\"tcp:mysql:3306*xorm_test/root/\" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-postgres - pull: default - image: golang:1.10 - depends_on: - - build - commands: - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -coverprofile=coverage4-1.txt -covermode=atomic" - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-postgres-schema - pull: default - image: golang:1.10 - depends_on: - - build - commands: - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic" - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-mssql - pull: default - image: golang:1.10 - depends_on: - - build - commands: - - "go test -v -race -db=\"mssql\" -conn_str=\"server=mssql;user id=sa;password=yourStrong(!)Password;database=xorm_test\" -coverprofile=coverage6-1.txt -covermode=atomic" - - "go test -v -race -db=\"mssql\" -conn_str=\"server=mssql;user id=sa;password=yourStrong(!)Password;database=xorm_test\" -cache=true -coverprofile=coverage6-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-tidb - pull: default - image: golang:1.10 - depends_on: - - build - commands: - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(tidb:4000)/xorm_test\" -ignore_select_update=true -coverprofile=coverage7-1.txt -covermode=atomic" - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(tidb:4000)/xorm_test\" -ignore_select_update=true -cache=true -coverprofile=coverage7-2.txt -covermode=atomic" - when: - event: - - push - - pull_request - -- name: test-end - pull: default - image: golang:1.10 - depends_on: - - test-sqlite - - test-mysql - - test-mysql-utf8mb4 - - test-mymysql - - test-postgres - - test-postgres-schema - - test-mssql - - test-tidb - commands: - - echo "go1.10 build end" - when: - event: - - push - - pull_request - -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: pgsql - pull: default - image: postgres:9.5 - environment: - POSTGRES_DB: xorm_test - POSTGRES_USER: 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 - ---- -kind: pipeline -name: go1.13-test -steps: -- name: build - pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" commands: - - go build -v - - go vet + - go vet ./... when: event: - push @@ -207,7 +17,7 @@ steps: - name: test-sqlite pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -221,7 +31,7 @@ steps: - name: test-mysql pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -235,7 +45,7 @@ steps: - name: test-mysql-utf8mb4 pull: default - image: golang:1.13 + image: golang:1.12 depends_on: - test-mysql environment: @@ -251,7 +61,7 @@ steps: - name: test-mymysql pull: default - image: golang:1.13 + image: golang:1.12 depends_on: - test-mysql-utf8mb4 environment: @@ -267,7 +77,7 @@ steps: - name: test-postgres pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -281,7 +91,7 @@ steps: - name: test-postgres-schema pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -295,7 +105,7 @@ steps: - name: test-mssql pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -309,7 +119,7 @@ steps: - name: test-tidb pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -323,7 +133,7 @@ steps: - name: merge_coverage pull: default - image: golang:1.13 + image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" From 4b28371f5f57095f81c05cdd61454f54a7219b0e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 19 Jan 2020 16:23:40 +0800 Subject: [PATCH 002/112] fix drone lint --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index d9d0c94e..867e41e0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,7 +2,7 @@ kind: pipeline name: testing steps: -- name: vet +- name: test-vet pull: default image: golang:1.12 environment: @@ -138,7 +138,7 @@ steps: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" depends_on: - - build + - test-vet - test-sqlite - test-mysql - test-mysql-utf8mb4 From 20f3d6870900bba6784fb92baa290f2bf52b3af5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 19 Jan 2020 16:28:52 +0800 Subject: [PATCH 003/112] fix vet --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 867e41e0..e9dae788 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,7 +9,7 @@ steps: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" commands: - - go vet ./... + - go vet when: event: - push From a18e35f7f5bd105bd39eceb0079a4f2b278444b3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 19 Jan 2020 09:36:06 +0000 Subject: [PATCH 004/112] SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 --- session_insert.go | 94 +++++++++++++++--------------------------- session_insert_test.go | 58 ++++++++++++++++++++++++++ session_update.go | 10 ++++- session_update_test.go | 45 ++++++++++++++++++++ statement_exprparam.go | 10 ++++- 5 files changed, 153 insertions(+), 64 deletions(-) diff --git a/session_insert.go b/session_insert.go index 1e19ce7a..5f8f7e1e 100644 --- a/session_insert.go +++ b/session_insert.go @@ -729,66 +729,7 @@ func (session *Session) insertMapInterface(m map[string]interface{}) (int64, err args = append(args, m[colName]) } - w := builder.NewWriter() - if session.statement.cond.IsValid() { - if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { - return 0, err - } - - if err := writeStrings(w, append(columns, exprs.colNames...), "`", "`"); err != nil { - return 0, err - } - - if _, err := w.WriteString(") SELECT "); err != nil { - return 0, err - } - - if err := session.statement.writeArgs(w, args); err != nil { - return 0, err - } - - if len(exprs.args) > 0 { - if _, err := w.WriteString(","); err != nil { - return 0, err - } - if err := exprs.writeArgs(w); err != nil { - return 0, err - } - } - - if _, err := w.WriteString(fmt.Sprintf(" FROM %s WHERE ", session.engine.Quote(tableName))); err != nil { - return 0, err - } - - if err := session.statement.cond.WriteTo(w); err != nil { - return 0, err - } - } else { - qm := strings.Repeat("?,", len(columns)) - qm = qm[:len(qm)-1] - - if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (`%s`) VALUES (%s)", session.engine.Quote(tableName), strings.Join(columns, "`,`"), qm)); err != nil { - return 0, err - } - w.Append(args...) - } - - sql := w.String() - args = w.Args() - - 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 + return session.insertMap(columns, args) } func (session *Session) insertMapString(m map[string]string) (int64, error) { @@ -808,6 +749,7 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { columns = append(columns, k) } } + sort.Strings(columns) var args = make([]interface{}, 0, len(m)) @@ -815,7 +757,18 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { args = append(args, m[colName]) } + return session.insertMap(columns, args) +} + +func (session *Session) insertMap(columns []string, args []interface{}) (int64, error) { + tableName := session.statement.TableName() + if len(tableName) <= 0 { + return 0, ErrTableNotFound + } + + exprs := session.statement.exprColumns w := builder.NewWriter() + // if insert where if session.statement.cond.IsValid() { if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { return 0, err @@ -853,10 +806,29 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { qm := strings.Repeat("?,", len(columns)) qm = qm[:len(qm)-1] - if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (`%s`) VALUES (%s)", session.engine.Quote(tableName), strings.Join(columns, "`,`"), qm)); err != nil { + if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { return 0, err } + + if err := writeStrings(w, append(columns, exprs.colNames...), "`", "`"); err != nil { + return 0, err + } + if _, err := w.WriteString(fmt.Sprintf(") VALUES (%s", qm)); err != nil { + return 0, err + } + w.Append(args...) + if len(exprs.args) > 0 { + if _, err := w.WriteString(","); err != nil { + return 0, err + } + if err := exprs.writeArgs(w); err != nil { + return 0, err + } + } + if _, err := w.WriteString(")"); err != nil { + return 0, err + } } sql := w.String() diff --git a/session_insert_test.go b/session_insert_test.go index e6100fdc..657d2b12 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -928,6 +928,64 @@ func TestInsertWhere(t *testing.T) { assert.EqualValues(t, 5, j5.Index) } +func TestInsertExpr2(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type InsertExprsRelease struct { + Id int64 + RepoId int + IsTag bool + IsDraft bool + NumCommits int + Sha1 string + } + + assertSync(t, new(InsertExprsRelease)) + + var ie = InsertExprsRelease{ + RepoId: 1, + IsTag: true, + } + inserted, err := testEngine. + SetExpr("is_draft", true). + SetExpr("num_commits", 0). + SetExpr("sha1", ""). + Insert(&ie) + assert.NoError(t, err) + assert.EqualValues(t, 1, inserted) + + var ie2 InsertExprsRelease + has, err := testEngine.ID(ie.Id).Get(&ie2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, true, ie2.IsDraft) + assert.EqualValues(t, "", ie2.Sha1) + assert.EqualValues(t, 0, ie2.NumCommits) + assert.EqualValues(t, 1, ie2.RepoId) + assert.EqualValues(t, true, ie2.IsTag) + + inserted, err = testEngine.Table(new(InsertExprsRelease)). + SetExpr("is_draft", true). + SetExpr("num_commits", 0). + SetExpr("sha1", ""). + Insert(map[string]interface{}{ + "repo_id": 1, + "is_tag": true, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, inserted) + + var ie3 InsertExprsRelease + has, err = testEngine.ID(ie.Id + 1).Get(&ie3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, true, ie3.IsDraft) + assert.EqualValues(t, "", ie3.Sha1) + assert.EqualValues(t, 0, ie3.NumCommits) + assert.EqualValues(t, 1, ie3.RepoId) + assert.EqualValues(t, true, ie3.IsTag) +} + type NightlyRate struct { ID int64 `xorm:"'id' not null pk BIGINT(20)" json:"id"` } diff --git a/session_update.go b/session_update.go index 231163e0..47ced66d 100644 --- a/session_update.go +++ b/session_update.go @@ -239,14 +239,20 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 for i, colName := range exprColumns.colNames { switch tp := exprColumns.args[i].(type) { case string: - colNames = append(colNames, session.engine.Quote(colName)+" = "+tp) + if len(tp) == 0 { + tp = "''" + } + colNames = append(colNames, session.engine.Quote(colName)+"="+tp) case *builder.Builder: subQuery, subArgs, err := builder.ToSQL(tp) if err != nil { return 0, err } - colNames = append(colNames, session.engine.Quote(colName)+" = ("+subQuery+")") + colNames = append(colNames, session.engine.Quote(colName)+"=("+subQuery+")") args = append(args, subArgs...) + default: + colNames = append(colNames, session.engine.Quote(colName)+"=?") + args = append(args, exprColumns.args[i]) } } diff --git a/session_update_test.go b/session_update_test.go index 386a68d1..d0ecef33 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -1359,3 +1359,48 @@ func TestUpdateAlias(t *testing.T) { assert.EqualValues(t, 2, ue.NumIssues) assert.EqualValues(t, "lunny xiao", ue.Name) } + +func TestUpdateExprs2(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UpdateExprsRelease struct { + Id int64 + RepoId int + IsTag bool + IsDraft bool + NumCommits int + Sha1 string + } + + assertSync(t, new(UpdateExprsRelease)) + + var uer = UpdateExprsRelease{ + RepoId: 1, + IsTag: false, + IsDraft: false, + NumCommits: 1, + Sha1: "sha1", + } + inserted, err := testEngine.Insert(&uer) + assert.NoError(t, err) + assert.EqualValues(t, 1, inserted) + + updated, err := testEngine. + Where("repo_id = ? AND is_tag = ?", 1, false). + SetExpr("is_draft", true). + SetExpr("num_commits", 0). + SetExpr("sha1", ""). + Update(new(UpdateExprsRelease)) + assert.NoError(t, err) + assert.EqualValues(t, 1, updated) + + var uer2 UpdateExprsRelease + has, err := testEngine.ID(uer.Id).Get(&uer2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, uer2.RepoId) + assert.EqualValues(t, false, uer2.IsTag) + assert.EqualValues(t, true, uer2.IsDraft) + assert.EqualValues(t, 0, uer2.NumCommits) + assert.EqualValues(t, "", uer2.Sha1) +} diff --git a/statement_exprparam.go b/statement_exprparam.go index 4da4f1ea..fc62e36f 100644 --- a/statement_exprparam.go +++ b/statement_exprparam.go @@ -69,10 +69,18 @@ func (exprs *exprParams) writeArgs(w *builder.BytesWriter) error { if _, err := w.WriteString(")"); err != nil { return err } - default: + 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 { From c37aff9b3a4a0f614f659c068dde0d62b7eb39bf Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Mon, 20 Jan 2020 02:45:00 +0000 Subject: [PATCH 005/112] Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 --- dialect_postgres.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dialect_postgres.go b/dialect_postgres.go index ccef3086..ac6d4fe8 100644 --- a/dialect_postgres.go +++ b/dialect_postgres.go @@ -901,7 +901,7 @@ func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { } func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { - if len(db.Schema) == 0 { + if len(db.Schema) == 0 || strings.Contains(tableName, ".") { return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", tableName, col.Name, db.SqlType(col)) } @@ -913,8 +913,8 @@ func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { quote := db.Quote idxName := index.Name - tableName = strings.Replace(tableName, `"`, "", -1) - tableName = strings.Replace(tableName, `.`, "_", -1) + tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") + tableName = tableParts[len(tableParts)-1] if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { From bd20ffba3b5c309c7bd9e6db0ccf5a4f705a7408 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 20 Jan 2020 05:24:12 +0000 Subject: [PATCH 006/112] fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 --- interface.go | 2 ++ session_update.go | 20 ++++++++++++-------- session_update_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/interface.go b/interface.go index a564db12..81a4b68a 100644 --- a/interface.go +++ b/interface.go @@ -92,6 +92,7 @@ type EngineInterface interface { Quote(string) string SetCacher(string, core.Cacher) SetConnMaxLifetime(time.Duration) + SetColumnMapper(core.IMapper) SetDefaultCacher(core.Cacher) SetLogger(logger core.ILogger) SetLogLevel(core.LogLevel) @@ -99,6 +100,7 @@ type EngineInterface interface { SetMaxOpenConns(int) SetMaxIdleConns(int) SetSchema(string) + SetTableMapper(core.IMapper) SetTZDatabase(tz *time.Location) SetTZLocation(tz *time.Location) ShowExecTime(...bool) diff --git a/session_update.go b/session_update.go index 47ced66d..22d516e7 100644 --- a/session_update.go +++ b/session_update.go @@ -300,21 +300,25 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 st := &session.statement - var sqlStr string - var condArgs []interface{} - var condSQL string - cond := session.statement.cond.And(autoCond) + var ( + sqlStr string + condArgs []interface{} + condSQL string + cond = session.statement.cond.And(autoCond) - var doIncVer = (table != nil && table.Version != "" && session.statement.checkVersion) - var verValue *reflect.Value + doIncVer = isStruct && (table != nil && table.Version != "" && session.statement.checkVersion) + verValue *reflect.Value + ) if doIncVer { verValue, err = table.VersionColumn().ValueOf(bean) if err != nil { return 0, err } - cond = cond.And(builder.Eq{session.engine.Quote(table.Version): verValue.Interface()}) - colNames = append(colNames, session.engine.Quote(table.Version)+" = "+session.engine.Quote(table.Version)+" + 1") + if verValue != nil { + cond = cond.And(builder.Eq{session.engine.Quote(table.Version): verValue.Interface()}) + colNames = append(colNames, session.engine.Quote(table.Version)+" = "+session.engine.Quote(table.Version)+" + 1") + } } condSQL, condArgs, err = builder.ToSQL(cond) diff --git a/session_update_test.go b/session_update_test.go index d0ecef33..274a6bfa 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -1404,3 +1404,45 @@ func TestUpdateExprs2(t *testing.T) { assert.EqualValues(t, 0, uer2.NumCommits) assert.EqualValues(t, "", uer2.Sha1) } + +func TestUpdateMap3(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UpdateMapUser struct { + Id uint64 `xorm:"PK autoincr"` + Name string `xorm:""` + Ver uint64 `xorm:"version"` + } + + oldMapper := testEngine.GetColumnMapper() + defer func() { + testEngine.SetColumnMapper(oldMapper) + }() + + mapper := core.NewPrefixMapper(core.SnakeMapper{}, "F") + testEngine.SetColumnMapper(mapper) + + assertSync(t, new(UpdateMapUser)) + + _, err := testEngine.Table(new(UpdateMapUser)).Insert(map[string]interface{}{ + "Fname": "first user name", + "Fver": 1, + }) + assert.NoError(t, err) + + update := map[string]interface{}{ + "Fname": "user name", + "Fver": 1, + } + rows, err := testEngine.Table(new(UpdateMapUser)).ID(1).Update(update) + assert.NoError(t, err) + assert.EqualValues(t, 1, rows) + + update = map[string]interface{}{ + "Name": "user name", + "Ver": 1, + } + rows, err = testEngine.Table(new(UpdateMapUser)).ID(1).Update(update) + assert.Error(t, err) + assert.EqualValues(t, 0, rows) +} From 14a0c19a0c92de6c3c65d6ba9f7251b3aaf77ae7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 20 Jan 2020 05:28:07 +0000 Subject: [PATCH 007/112] fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 --- session_iterate.go | 27 +++++++++++++++------------ session_iterate_test.go | 11 +++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/session_iterate.go b/session_iterate.go index ca996c28..a1642bb3 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -4,7 +4,9 @@ package xorm -import "reflect" +import ( + "reflect" +) // IterFunc only use by Iterate type IterFunc func(idx int, bean interface{}) error @@ -60,10 +62,6 @@ func (session *Session) BufferSize(size int) *Session { } func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { - if session.isAutoClose { - defer session.Close() - } - var bufferSize = session.statement.bufferSize var limit = session.statement.LimitN if limit > 0 && bufferSize > limit { @@ -73,9 +71,14 @@ func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { v := rValue(bean) sliceType := reflect.SliceOf(v.Type()) var idx = 0 - for { + session.autoResetStatement = false + defer func() { + session.autoResetStatement = true + }() + + for bufferSize > 0 { slice := reflect.New(sliceType) - if err := session.Limit(bufferSize, start).find(slice.Interface(), bean); err != nil { + if err := session.NoCache().Limit(bufferSize, start).find(slice.Interface(), bean); err != nil { return err } @@ -86,13 +89,13 @@ func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { idx++ } - start = start + slice.Elem().Len() - if limit > 0 && idx+bufferSize > limit { - bufferSize = limit - idx + if bufferSize > slice.Elem().Len() { + break } - if bufferSize <= 0 || slice.Elem().Len() < bufferSize || idx == limit { - break + start = start + slice.Elem().Len() + if limit > 0 && start+bufferSize > limit { + bufferSize = limit - start } } diff --git a/session_iterate_test.go b/session_iterate_test.go index 9a7ec25f..bb0c59c4 100644 --- a/session_iterate_test.go +++ b/session_iterate_test.go @@ -89,4 +89,15 @@ func TestBufferIterate(t *testing.T) { }) assert.NoError(t, err) assert.EqualValues(t, 7, cnt) + + cnt = 0 + 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) + cnt++ + return nil + }) + assert.NoError(t, err) + assert.EqualValues(t, 10, cnt) } From 6dfe337869d9e6cf5be8b39b441207ef6472cb6c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 20 Jan 2020 08:22:06 +0000 Subject: [PATCH 008/112] fix statement.LimitN(0) will delete or update all data (#1119) fix test fix nil pointer fix statement.Limit(0) will update or delete all data fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple slices with customize table name (#1433) * fix bug when insert multiple slices with customize table name * fix tests on mssql * fix tests fix insert where with bool bug on mssql (#1432) fix setexpr missing big quotes (#1431) * fix setexpr missing big quotes * fix tests * fix tests Add support subquery on SetExpr (#1428) * add support subquery on SetExpr * fix tests fix go mod (#1427) fix tests (#1429) Use strings.Builder instead of builder.StringBuilder (#1417) * use strings.Builder instead of builder.StringBuilder * fix dependency * fix dependency Remove unuse get cols code (#1413) Add mssql ci test (#1410) * add mssql ci test * fix drone test Add insert select where support (#1401) Use drone new format (#1388) * use drone new format fix get customize type bug (#1382) fix bugs (#1375) update drone (#1374) Add tests for get var (#1305) * add test for SQL get * fix tests fix error when get null var (#890) * fix error when get null var * add support get for null var * fix bug Remove quotestr totally (#1366) * remove QuoteStr() totally * update xorm.core -> v0.7.0 * update dialect Quote remove QuoteStr() usage in dialects (#1364) document of FindAndCount() (#1365) remove QuoteStr() usage (#1360) Co-authored-by: yifhao Co-authored-by: yifhao <1124210681@qq.com> Co-authored-by: Guillermo Prandi Co-authored-by: Guillermo Prandi Co-authored-by: yudppp Co-authored-by: BetaCat Reviewed-on: https://gitea.com/xorm/xorm/pulls/1119 --- session_delete.go | 12 +++++++----- session_iterate.go | 10 +++++----- session_update.go | 13 +++++++------ statement.go | 31 +++++++++++++++++++------------ tag_id_test.go | 4 ++-- 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/session_delete.go b/session_delete.go index 675d4d8c..7b0a0641 100644 --- a/session_delete.go +++ b/session_delete.go @@ -101,7 +101,8 @@ func (session *Session) Delete(bean interface{}) (int64, error) { if err != nil { return 0, err } - if len(condSQL) == 0 && session.statement.LimitN == 0 { + pLimitN := session.statement.LimitN + if len(condSQL) == 0 && (pLimitN == nil || *pLimitN == 0) { return 0, ErrNeedDeletedCond } @@ -119,8 +120,9 @@ func (session *Session) Delete(bean interface{}) (int64, error) { if len(session.statement.OrderStr) > 0 { orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr) } - if session.statement.LimitN > 0 { - orderSQL += fmt.Sprintf(" LIMIT %d", session.statement.LimitN) + if pLimitN != nil && *pLimitN > 0 { + limitNValue := *pLimitN + orderSQL += fmt.Sprintf(" LIMIT %d", limitNValue) } if len(orderSQL) > 0 { @@ -139,7 +141,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { } else { deleteSQL += " WHERE " + inSQL } - // TODO: how to handle delete limit on mssql? + // TODO: how to handle delete limit on mssql? case core.MSSQL: return 0, ErrNotImplemented default: @@ -180,7 +182,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { } else { realSQL += " WHERE " + inSQL } - // TODO: how to handle delete limit on mssql? + // TODO: how to handle delete limit on mssql? case core.MSSQL: return 0, ErrNotImplemented default: diff --git a/session_iterate.go b/session_iterate.go index a1642bb3..4a3cc083 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -63,9 +63,9 @@ func (session *Session) BufferSize(size int) *Session { func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { var bufferSize = session.statement.bufferSize - var limit = session.statement.LimitN - if limit > 0 && bufferSize > limit { - bufferSize = limit + var pLimitN = session.statement.LimitN + if pLimitN != nil && bufferSize > *pLimitN { + bufferSize = *pLimitN } var start = session.statement.Start v := rValue(bean) @@ -94,8 +94,8 @@ func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { } start = start + slice.Elem().Len() - if limit > 0 && start+bufferSize > limit { - bufferSize = limit - start + if pLimitN != nil && start+bufferSize > *pLimitN { + bufferSize = *pLimitN - start } } diff --git a/session_update.go b/session_update.go index 22d516e7..a26b15a6 100644 --- a/session_update.go +++ b/session_update.go @@ -337,11 +337,12 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var tableName = session.statement.TableName() // TODO: Oracle support needed var top string - if st.LimitN > 0 { + if st.LimitN != nil { + limitValue := *st.LimitN if st.Engine.dialect.DBType() == core.MYSQL { - condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) + condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) } else if st.Engine.dialect.DBType() == core.SQLITE { - tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) + tempCondSQL := condSQL + fmt.Sprintf(" 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 = builder.ToSQL(cond) @@ -352,7 +353,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 condSQL = "WHERE " + condSQL } } else if st.Engine.dialect.DBType() == core.POSTGRES { - tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) + tempCondSQL := condSQL + fmt.Sprintf(" 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 = builder.ToSQL(cond) @@ -367,7 +368,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if st.OrderStr != "" && st.Engine.dialect.DBType() == core.MSSQL && table != nil && len(table.PrimaryKeys) == 1 { cond = builder.Expr(fmt.Sprintf("%s IN (SELECT TOP (%d) %s FROM %v%v)", - table.PrimaryKeys[0], st.LimitN, table.PrimaryKeys[0], + table.PrimaryKeys[0], limitValue, table.PrimaryKeys[0], session.engine.Quote(tableName), condSQL), condArgs...) condSQL, condArgs, err = builder.ToSQL(cond) @@ -378,7 +379,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 condSQL = "WHERE " + condSQL } } else { - top = fmt.Sprintf("TOP (%d) ", st.LimitN) + top = fmt.Sprintf("TOP (%d) ", limitValue) } } } diff --git a/statement.go b/statement.go index 67e35213..dc251d30 100644 --- a/statement.go +++ b/statement.go @@ -20,7 +20,7 @@ type Statement struct { RefTable *core.Table Engine *Engine Start int - LimitN int + LimitN *int idParam *core.PK OrderStr string JoinStr string @@ -65,7 +65,7 @@ type Statement struct { func (statement *Statement) Init() { statement.RefTable = nil statement.Start = 0 - statement.LimitN = 0 + statement.LimitN = nil statement.OrderStr = "" statement.UseCascade = true statement.JoinStr = "" @@ -671,7 +671,7 @@ func (statement *Statement) Top(limit int) *Statement { // Limit generate LIMIT start, limit statement func (statement *Statement) Limit(limit int, start ...int) *Statement { - statement.LimitN = limit + statement.LimitN = &limit if len(start) > 0 { statement.Start = start[0] } @@ -1071,9 +1071,11 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr) } + pLimitN := statement.LimitN if dialect.DBType() == core.MSSQL { - if statement.LimitN > 0 { - top = fmt.Sprintf("TOP %d ", statement.LimitN) + if pLimitN != nil { + LimitNValue := *pLimitN + top = fmt.Sprintf("TOP %d ", LimitNValue) } if statement.Start > 0 { var column string @@ -1134,12 +1136,16 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n if needLimit { if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE { if statement.Start > 0 { - fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", statement.LimitN, statement.Start) - } else if statement.LimitN > 0 { - fmt.Fprint(&buf, " LIMIT ", statement.LimitN) + 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) } } else if dialect.DBType() == core.ORACLE { - if statement.Start != 0 || statement.LimitN != 0 { + if statement.Start != 0 || pLimitN != nil { oldString := buf.String() buf.Reset() rawColStr := columnStr @@ -1147,7 +1153,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n rawColStr = "at.*" } 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+statement.LimitN, statement.Start) + columnStr, rawColStr, oldString, statement.Start+*pLimitN, statement.Start) } } } @@ -1204,8 +1210,9 @@ func (statement *Statement) convertIDSQL(sqlStr string) string { } var top string - if statement.LimitN > 0 && statement.Engine.dialect.DBType() == core.MSSQL { - top = fmt.Sprintf("TOP %d ", statement.LimitN) + pLimitN := statement.LimitN + if pLimitN != nil && statement.Engine.dialect.DBType() == core.MSSQL { + top = fmt.Sprintf("TOP %d ", *pLimitN) } newsql := fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1]) diff --git a/tag_id_test.go b/tag_id_test.go index f1c5a6bc..dce5f688 100644 --- a/tag_id_test.go +++ b/tag_id_test.go @@ -7,8 +7,8 @@ package xorm import ( "testing" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) type IDGonicMapper struct { @@ -76,7 +76,7 @@ func TestSameMapperID(t *testing.T) { for _, tb := range tables { if tb.Name == "IDSameMapper" { if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "ID" { - t.Fatal(tb) + t.Fatalf("tb %s tb.PKColumns() is %d not 1, tb.PKColumns()[0].Name is %s not ID", tb.Name, len(tb.PKColumns()), tb.PKColumns()[0].Name) } return } From 43b364ccfd3ec5cd6935a69db9fcb23b2a27e7c6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 20 Jan 2020 08:23:54 +0000 Subject: [PATCH 009/112] Allow update created field if indicated on Cols (#1513) Allow update created field if indicated on Cols Reviewed-on: https://gitea.com/xorm/xorm/pulls/1513 --- statement.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statement.go b/statement.go index dc251d30..ea3ecabe 100644 --- a/statement.go +++ b/statement.go @@ -247,7 +247,7 @@ func (statement *Statement) buildUpdates(bean interface{}, if !includeVersion && col.IsVersion { continue } - if col.IsCreated { + if col.IsCreated && !columnMap.contain(col.Name) { continue } if !includeUpdated && col.IsUpdated { From 062d9960b227d77e6c21b7ff79a9b46b199cb94d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 20 Jan 2020 08:25:21 +0000 Subject: [PATCH 010/112] For nullable columns, store nil values as NULL (#531) Merge branch 'master' into jcsalem/fix/nil_ptr_is_nullable fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 For nullable columns, store nil values as NULL fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple slices with customize table name (#1433) * fix bug when insert multiple slices with customize table name * fix tests on mssql * fix tests fix insert where with bool bug on mssql (#1432) fix setexpr missing big quotes (#1431) * fix setexpr missing big quotes * fix tests * fix tests Add support subquery on SetExpr (#1428) * add support subquery on SetExpr * fix tests fix go mod (#1427) fix tests (#1429) Use strings.Builder instead of builder.StringBuilder (#1417) * use strings.Builder instead of builder.StringBuilder * fix dependency * fix dependency Remove unuse get cols code (#1413) Add mssql ci test (#1410) * add mssql ci test * fix drone test Add insert select where support (#1401) Use drone new format (#1388) * use drone new format fix get customize type bug (#1382) fix bugs (#1375) update drone (#1374) Add tests for get var (#1305) * add test for SQL get * fix tests fix error when get null var (#890) * fix error when get null var * add support get for null var * fix bug Remove quotestr totally (#1366) * remove QuoteStr() totally * update xorm.core -> v0.7.0 * update dialect Quote remove QuoteStr() usage in dialects (#1364) document of FindAndCount() (#1365) remove QuoteStr() usage (#1360) make sure timeout in context timeout t... Co-authored-by: Guillermo Prandi Co-authored-by: Jim Salem Co-authored-by: Guillermo Prandi Co-authored-by: yudppp Co-authored-by: BetaCat Reviewed-on: https://gitea.com/xorm/xorm/pulls/531 --- helpers.go | 11 +++++++++++ session_insert.go | 2 +- session_update.go | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/helpers.go b/helpers.go index a31e922c..aadeb6dc 100644 --- a/helpers.go +++ b/helpers.go @@ -155,6 +155,17 @@ func isZero(k interface{}) bool { return false } +func isZeroValue(v reflect.Value) bool { + if isZero(v.Interface()) { + return true + } + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + return false +} + func isStructZero(v reflect.Value) bool { if !v.IsValid() { return true diff --git a/session_insert.go b/session_insert.go index 5f8f7e1e..c9859b7a 100644 --- a/session_insert.go +++ b/session_insert.go @@ -674,7 +674,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac // !evalphobia! set fieldValue as nil when column is nullable and zero-value if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok { - if col.Nullable && isZero(fieldValue.Interface()) { + if col.Nullable && isZeroValue(fieldValue) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) } diff --git a/session_update.go b/session_update.go index a26b15a6..18425ec3 100644 --- a/session_update.go +++ b/session_update.go @@ -517,7 +517,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac // !evalphobia! set fieldValue as nil when column is nullable and zero-value if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok { - if col.Nullable && isZero(fieldValue.Interface()) { + if col.Nullable && isZeroValue(fieldValue) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) } From e96d53762a424310280509be962293784ac2b322 Mon Sep 17 00:00:00 2001 From: kusana Date: Wed, 19 Feb 2020 09:25:41 +0000 Subject: [PATCH 011/112] Fixed Join strings on func Exist (#1520) fix test fixed Join strings on func Exist Co-authored-by: Tomofumi Kusana Reviewed-on: https://gitea.com/xorm/xorm/pulls/1520 --- session_exist.go | 17 ++++--- session_exist_test.go | 103 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/session_exist.go b/session_exist.go index 660cc47e..bce2758d 100644 --- a/session_exist.go +++ b/session_exist.go @@ -25,8 +25,8 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { var sqlStr string var args []interface{} + var joinStr string var err error - if session.statement.RawSQL == "" { if len(bean) == 0 { tableName := session.statement.TableName() @@ -35,6 +35,9 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { } tableName = session.statement.Engine.Quote(tableName) + if len(session.statement.JoinStr) > 0 { + joinStr = session.statement.JoinStr + } if session.statement.cond.IsValid() { condSQL, condArgs, err := builder.ToSQL(session.statement.cond) @@ -43,20 +46,20 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { } if session.engine.dialect.DBType() == core.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s WHERE %s", tableName, condSQL) + sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) } else if session.engine.dialect.DBType() == core.ORACLE { - sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) AND ROWNUM=1", tableName, condSQL) + sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) %s AND ROWNUM=1", tableName, joinStr, condSQL) } else { - sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT 1", tableName, condSQL) + sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL) } args = condArgs } else { if session.engine.dialect.DBType() == core.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s", tableName) + sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) } else if session.engine.dialect.DBType() == core.ORACLE { - sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE ROWNUM=1", tableName) + sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE ROWNUM=1", tableName, joinStr) } else { - sqlStr = fmt.Sprintf("SELECT * FROM %s LIMIT 1", tableName) + sqlStr = fmt.Sprintf("SELECT * FROM %s %s LIMIT 1", tableName, joinStr) } args = []interface{}{} } diff --git a/session_exist_test.go b/session_exist_test.go index 9d985771..52d39f32 100644 --- a/session_exist_test.go +++ b/session_exist_test.go @@ -74,3 +74,106 @@ func TestExistStruct(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func TestExistStructForJoin(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type Number struct { + Id int64 + Lid int64 + } + + type OrderList struct { + Id int64 + Eid int64 + } + + type Player struct { + Id int64 + Name string + } + + assert.NoError(t, testEngine.Sync2(new(Number), new(OrderList), new(Player))) + + var ply Player + cnt, err := testEngine.Insert(&ply) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var orderlist = OrderList{ + Eid: ply.Id, + } + cnt, err = testEngine.Insert(&orderlist) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var um = Number{ + Lid: orderlist.Id, + } + cnt, err = testEngine.Insert(&um) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + session := testEngine.NewSession() + 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) + 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) + 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) + 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) + 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") + has, err = session.Exist() + assert.NoError(t, err) + assert.True(t, has) + + err = session.DropTable("order_list") + assert.NoError(t, err) + + session.Table("number"). + Select("player.id"). + 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") + has, err = session.Exist() + assert.NoError(t, err) + assert.True(t, has) +} From a9e2ecbf44516f5bb82743776cec8d7b8dcf9ad4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2020 03:41:19 +0000 Subject: [PATCH 012/112] Fix wrong warning log on autoincrement column when sync table (#1525) improve doc Fix wrong warning log on autoincrement column when sync table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1525 --- session_schema.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/session_schema.go b/session_schema.go index 5e576c29..d4c71374 100644 --- a/session_schema.go +++ b/session_schema.go @@ -346,10 +346,12 @@ func (session *Session) Sync2(beans ...interface{}) error { } if col.Default != oriCol.Default { - if (col.SQLType.Name == core.Bool || col.SQLType.Name == core.Boolean) && + switch { + case col.IsAutoIncrement: // For autoincrement column, don't check default + case (col.SQLType.Name == core.Bool || col.SQLType.Name == core.Boolean) && ((strings.EqualFold(col.Default, "true") && oriCol.Default == "1") || - (strings.EqualFold(col.Default, "false") && oriCol.Default == "0")) { - } else { + (strings.EqualFold(col.Default, "false") && oriCol.Default == "0")): + default: engine.logger.Warnf("Table %s Column %s db default is %s, struct default is %s", tbName, col.Name, oriCol.Default, col.Default) } From aefe7fd6daa962910253879aa37ee428462a3178 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2020 06:56:01 +0000 Subject: [PATCH 013/112] Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 --- session_exist_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/session_exist_test.go b/session_exist_test.go index 52d39f32..2792654f 100644 --- a/session_exist_test.go +++ b/session_exist_test.go @@ -162,6 +162,10 @@ func TestExistStructForJoin(t *testing.T) { err = session.DropTable("order_list") assert.NoError(t, err) + exist, err := session.IsTableExist("order_list") + assert.NoError(t, err) + assert.False(t, exist) + session.Table("number"). Select("player.id"). Join("INNER", "order_list", "order_list.id = number.lid"). From 189e2727740e872839026d302253bd0ff1724563 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2020 07:18:44 +0000 Subject: [PATCH 014/112] Add tests for table name (#1517) add tests for table name Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 --- engine.go | 4 +-- engine_table.go | 20 ++----------- session_get_test.go | 2 +- table_name.go | 31 +++++++++++++++++++ table_name_test.go | 73 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 table_name.go create mode 100644 table_name_test.go diff --git a/engine.go b/engine.go index a7e52ea4..286ce766 100644 --- a/engine.go +++ b/engine.go @@ -215,7 +215,7 @@ func quoteTo(buf *strings.Builder, quotePair string, value string) { _, _ = buf.WriteString(value) return } - + prefix, suffix := quotePair[0], quotePair[1] i := 0 @@ -921,7 +921,7 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { t := v.Type() table := core.NewEmptyTable() table.Type = t - table.Name = engine.tbNameForMap(v) + table.Name = getTableName(engine.TableMapper, v) var idFieldColName string var hasCacheTag, hasNoCacheTag bool diff --git a/engine_table.go b/engine_table.go index eb5aa850..87388a35 100644 --- a/engine_table.go +++ b/engine_table.go @@ -44,20 +44,6 @@ func (session *Session) tbNameNoSchema(table *core.Table) string { return table.Name } -func (engine *Engine) tbNameForMap(v reflect.Value) string { - if v.Type().Implements(tpTableName) { - return v.Interface().(TableName).TableName() - } - if v.Kind() == reflect.Ptr { - v = v.Elem() - if v.Type().Implements(tpTableName) { - return v.Interface().(TableName).TableName() - } - } - - return engine.TableMapper.Obj2Table(v.Type().Name()) -} - func (engine *Engine) tbNameNoSchema(tablename interface{}) string { switch tablename.(type) { case []string: @@ -82,7 +68,7 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { v := rValue(f) t := v.Type() if t.Kind() == reflect.Struct { - table = engine.tbNameForMap(v) + table = getTableName(engine.TableMapper, v) } else { table = engine.Quote(fmt.Sprintf("%v", f)) } @@ -100,12 +86,12 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { return tablename.(string) case reflect.Value: v := tablename.(reflect.Value) - return engine.tbNameForMap(v) + return getTableName(engine.TableMapper, v) default: v := rValue(tablename) t := v.Type() if t.Kind() == reflect.Struct { - return engine.tbNameForMap(v) + return getTableName(engine.TableMapper, v) } return engine.Quote(fmt.Sprintf("%v", tablename)) } diff --git a/session_get_test.go b/session_get_test.go index fcef992e..54ba8916 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -480,7 +480,7 @@ type MyGetCustomTableImpletation struct { const getCustomTableName = "GetCustomTableInterface" -func (m *MyGetCustomTableImpletation) TableName() string { +func (MyGetCustomTableImpletation) TableName() string { return getCustomTableName } diff --git a/table_name.go b/table_name.go new file mode 100644 index 00000000..632c2879 --- /dev/null +++ b/table_name.go @@ -0,0 +1,31 @@ +// Copyright 2020 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 ( + "reflect" + + "xorm.io/core" +) + +func getTableName(mapper core.IMapper, v reflect.Value) string { + if t, ok := v.Interface().(TableName); ok { + return t.TableName() + } + if v.Type().Implements(tpTableName) { + return v.Interface().(TableName).TableName() + } + if v.Kind() == reflect.Ptr { + v = v.Elem() + if t, ok := v.Interface().(TableName); ok { + return t.TableName() + } + if v.Type().Implements(tpTableName) { + return v.Interface().(TableName).TableName() + } + } + + return mapper.Obj2Table(v.Type().Name()) +} diff --git a/table_name_test.go b/table_name_test.go new file mode 100644 index 00000000..6cb0ceaa --- /dev/null +++ b/table_name_test.go @@ -0,0 +1,73 @@ +// Copyright 2020 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 ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + "xorm.io/core" +) + +type TestTableNameStruct struct{} + +func (t *TestTableNameStruct) TableName() string { + return "my_test_table_name_struct" +} + +func TestGetTableName(t *testing.T) { + var kases = []struct { + mapper core.IMapper + v reflect.Value + expectedTableName string + }{ + { + core.SnakeMapper{}, + reflect.ValueOf(new(Userinfo)), + "userinfo", + }, + { + core.SnakeMapper{}, + reflect.ValueOf(Userinfo{}), + "userinfo", + }, + { + core.SameMapper{}, + reflect.ValueOf(new(Userinfo)), + "Userinfo", + }, + { + core.SameMapper{}, + reflect.ValueOf(Userinfo{}), + "Userinfo", + }, + { + core.SnakeMapper{}, + reflect.ValueOf(new(MyGetCustomTableImpletation)), + getCustomTableName, + }, + { + core.SnakeMapper{}, + reflect.ValueOf(MyGetCustomTableImpletation{}), + getCustomTableName, + }, + { + core.SnakeMapper{}, + reflect.ValueOf(MyGetCustomTableImpletation{}), + getCustomTableName, + }, + { + core.SnakeMapper{}, + reflect.ValueOf(new(TestTableNameStruct)), + new(TestTableNameStruct).TableName(), + }, + } + + for _, kase := range kases { + assert.EqualValues(t, kase.expectedTableName, getTableName(kase.mapper, kase.v)) + } +} From 2513e09caa48d96cbbe0870bf9cf15db72354d91 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2020 07:52:44 +0000 Subject: [PATCH 015/112] Add test for second insert error (#1527) Add test for second insert error Reviewed-on: https://gitea.com/xorm/xorm/pulls/1527 --- error.go | 2 +- session_insert.go | 27 ++++++++++++++++----------- session_insert_test.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/error.go b/error.go index a67527ac..2e9cbfaa 100644 --- a/error.go +++ b/error.go @@ -27,7 +27,7 @@ var ( // ErrConditionType condition type unsupported ErrConditionType = errors.New("Unsupported condition type") // ErrUnSupportedSQLType parameter of SQL is not supported - ErrUnSupportedSQLType = errors.New("unsupported sql type") + ErrUnSupportedSQLType = errors.New("Unsupported sql type") ) // ErrFieldIsNotExist columns does not exist diff --git a/session_insert.go b/session_insert.go index c9859b7a..fb67db16 100644 --- a/session_insert.go +++ b/session_insert.go @@ -16,6 +16,9 @@ import ( "xorm.io/core" ) +// ErrNoElementsOnSlice represents an error there is no element 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) { var affected int64 @@ -67,21 +70,23 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) { sliceValue := reflect.Indirect(reflect.ValueOf(bean)) if sliceValue.Kind() == reflect.Slice { size := sliceValue.Len() - if size > 0 { - if session.engine.SupportInsertMany() { - cnt, err := session.innerInsertMulti(bean) + if size <= 0 { + return 0, ErrNoElementsOnSlice + } + + if session.engine.SupportInsertMany() { + cnt, err := session.innerInsertMulti(bean) + if err != nil { + return affected, err + } + affected += cnt + } else { + for i := 0; i < size; i++ { + cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) if err != nil { return affected, err } affected += cnt - } else { - for i := 0; i < size; i++ { - cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) - if err != nil { - return affected, err - } - affected += cnt - } } } } else { diff --git a/session_insert_test.go b/session_insert_test.go index 657d2b12..9cd5b86a 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -1067,3 +1067,38 @@ func TestInsertMultiWithOmit(t *testing.T) { assert.EqualValues(t, 3, num) check() } + +func TestInsertTwice(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type InsertStructA struct { + FieldA int + } + + type InsertStructB struct { + FieldB int + } + + assert.NoError(t, testEngine.Sync2(new(InsertStructA), new(InsertStructB))) + + var sliceA []InsertStructA // sliceA is empty + sliceB := []InsertStructB{ + InsertStructB{ + FieldB: 1, + }, + } + + ssn := testEngine.NewSession() + defer ssn.Close() + + err := ssn.Begin() + assert.NoError(t, err) + + _, err = ssn.Insert(sliceA) + assert.EqualValues(t, ErrNoElementsOnSlice, err) + + _, err = ssn.Insert(sliceB) + assert.NoError(t, err) + + assert.NoError(t, ssn.Commit()) +} From d874b64b715e256dd3fd43e500a48047d4d099d3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2020 08:16:13 +0000 Subject: [PATCH 016/112] format time when sqlTypeName is core.Varchar (#1026) fix time test add test for time format sign codes according to contributing rules. format time when sqlTypeName is core.Varchar. Same with core.DateTime or core.TimeStamp Add test for second insert error (#1527) Add test for second insert error Reviewed-on: https://gitea.com/xorm/xorm/pulls/1527 Add tests for table name (#1517) add tests for table name Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix wrong warning log on autoincrement column when sync table (#1525) improve doc Fix wrong warning log on autoincrement column when sync table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1525 Fixed Join strings on func Exist (#1520) fix test fixed Join strings on func Exist Co-authored-by: Tomofumi Kusana Reviewed-on: https://gitea.com/xorm/xorm/pulls/1520 For nullable columns, store nil values as NULL (#531) Merge branch 'master' into jcsalem/fix/nil_ptr_is_nullable fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 For nullable columns, store nil values as NULL fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple slices with customize table name (#1433) * fix bug when insert multiple slices with customize table name * fix tests on mssql * fix tests fix insert where with bool bug on mssql (#1432) fix setexpr missing big quotes (#1431) * fix setexpr missing big quotes * fix tests * fix tests Add support subquery on SetExpr (#1428) * add support subquery on SetExpr * fix tests fix go mod (#1427) ... Co-authored-by: DarthPestilane Co-authored-by: kusana Co-authored-by: Guillermo Prandi Co-authored-by: Guillermo Prandi Co-authored-by: yudppp Reviewed-on: https://gitea.com/xorm/xorm/pulls/1026 --- .gitignore | 1 + engine.go | 2 +- time_test.go | 11 +++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f1757b98..3629e49a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Folders _obj _test +vendor/ # Architecture specific extensions/prefixes *.[568vq] diff --git a/engine.go b/engine.go index 286ce766..b76d4c6d 100644 --- a/engine.go +++ b/engine.go @@ -1621,7 +1621,7 @@ func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{} v = s[11:19] case core.Date: v = t.Format("2006-01-02") - case core.DateTime, core.TimeStamp: + case core.DateTime, core.TimeStamp, core.Varchar: // !DarthPestilane! format time when sqlTypeName is core.Varchar. v = t.Format("2006-01-02 15:04:05") case core.TimeStampz: if engine.dialect.DBType() == core.MSSQL { diff --git a/time_test.go b/time_test.go index b7e4d12b..9a821983 100644 --- a/time_test.go +++ b/time_test.go @@ -255,10 +255,12 @@ func TestTimeUserDeleted(t *testing.T) { assert.NoError(t, prepareEngine()) type UserDeleted struct { - Id string - CreatedAt time.Time `xorm:"created"` - UpdatedAt time.Time `xorm:"updated"` - DeletedAt time.Time `xorm:"deleted"` + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + DeletedAt time.Time `xorm:"deleted"` + CreatedAtStr string `xorm:"datetime created"` + UpdatedAtStr string `xorm:"datetime updated"` } assertSync(t, new(UserDeleted)) @@ -282,6 +284,7 @@ func TestTimeUserDeleted(t *testing.T) { assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) assert.True(t, isTimeZero(user2.DeletedAt)) fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) + fmt.Println("user2 str", user2.CreatedAtStr, user2.UpdatedAtStr) var user3 UserDeleted cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) From bc9947c62b8c35b4f9904ab931e9d93bf333512b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2020 09:52:49 +0000 Subject: [PATCH 017/112] Add password for postgres drone image (#1530) Add password for postgres drone image Reviewed-on: https://gitea.com/xorm/xorm/pulls/1530 --- .drone.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index e9dae788..a4fb812d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -82,8 +82,8 @@ steps: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" commands: - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -coverprofile=coverage4-1.txt -covermode=atomic" - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic" + - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -coverprofile=coverage4-1.txt -covermode=atomic" + - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic" when: event: - push @@ -96,8 +96,8 @@ steps: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" commands: - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic" - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic" + - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic" + - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic" when: event: - push @@ -175,6 +175,7 @@ services: environment: POSTGRES_DB: xorm_test POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres when: event: - push From 22aef2cd3285c77bad9d6465303160530a0dae85 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 01:38:47 +0000 Subject: [PATCH 018/112] Add makefile (#1531) Fix drone Fix ci Add deps Improve drone Fix envs Add makefile Reviewed-on: https://gitea.com/xorm/xorm/pulls/1531 --- .circleci/config.yml | 61 ----- .drone.yml | 240 ++++++++++++++++-- .revive.toml | 25 ++ Makefile | 190 ++++++++++++++ cache_lru_test.go | 2 +- cache_memory_store_test.go | 4 +- engine.go | 6 +- examples/{ => cache}/cache.go | 2 +- .../cache_goroutine.go} | 2 +- examples/{ => conversion}/conversion.go | 2 +- examples/{ => derive}/derive.go | 2 +- examples/{ => find}/find.go | 2 +- examples/{ => goroutine}/goroutine.go | 2 +- .../single_mapping.go} | 2 +- .../max_connect.go} | 2 +- examples/{ => sync}/sync.go | 2 +- examples/{ => tables}/tables.go | 2 +- migrate/migrate_test.go | 2 +- session_cond_test.go | 2 +- session_delete_test.go | 2 +- session_find_test.go | 2 +- session_insert_test.go | 26 +- session_pk_test.go | 2 +- session_stats_test.go | 2 +- session_tx_test.go | 2 +- session_update_test.go | 6 +- tag.go | 2 +- tag_extends_test.go | 2 +- tag_version_test.go | 4 +- test_mssql.sh | 1 - test_mssql_cache.sh | 1 - test_mymysql.sh | 1 - test_mymysql_cache.sh | 1 - test_mysql.sh | 1 - test_mysql_cache.sh | 1 - test_postgres.sh | 1 - test_postgres_cache.sh | 1 - test_sqlite.sh | 1 - test_sqlite_cache.sh | 1 - test_tidb.sh | 1 - types_test.go | 22 +- 41 files changed, 485 insertions(+), 150 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .revive.toml create mode 100644 Makefile rename examples/{ => cache}/cache.go (100%) rename examples/{cachegoroutine.go => cache_gorountine/cache_goroutine.go} (100%) rename examples/{ => conversion}/conversion.go (100%) rename examples/{ => derive}/derive.go (100%) rename examples/{ => find}/find.go (94%) rename examples/{ => goroutine}/goroutine.go (100%) rename examples/{singlemapping.go => mapping/single_mapping.go} (100%) rename examples/{maxconnect.go => max_connect/max_connect.go} (100%) rename examples/{ => sync}/sync.go (100%) rename examples/{ => tables}/tables.go (100%) delete mode 100755 test_mssql.sh delete mode 100755 test_mssql_cache.sh delete mode 100755 test_mymysql.sh delete mode 100755 test_mymysql_cache.sh delete mode 100755 test_mysql.sh delete mode 100755 test_mysql_cache.sh delete mode 100755 test_postgres.sh delete mode 100755 test_postgres_cache.sh delete mode 100755 test_sqlite.sh delete mode 100755 test_sqlite_cache.sh delete mode 100755 test_tidb.sh diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c8f64282..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Golang CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-go/ for more details -version: 2 -jobs: - build: - docker: - # specify the version - - image: circleci/golang:1.10 - - - image: circleci/mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: true - MYSQL_DATABASE: xorm_test - MYSQL_HOST: 127.0.0.1 - MYSQL_ROOT_HOST: '%' - MYSQL_USER: root - - # CircleCI PostgreSQL images available at: https://hub.docker.com/r/circleci/postgres/ - - image: circleci/postgres:9.6.2-alpine - environment: - POSTGRES_USER: circleci - POSTGRES_DB: xorm_test - - - image: microsoft/mssql-server-linux:latest - environment: - ACCEPT_EULA: Y - SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Developer - - - image: pingcap/tidb:v2.1.2 - - working_directory: /go/src/github.com/go-xorm/xorm - steps: - - checkout - - - run: go get -t -d -v ./... - - run: go get -u xorm.io/core - - run: go get -u xorm.io/builder - - run: GO111MODULE=off go build -v - - run: GO111MODULE=on go build -v - - - run: go get -u github.com/wadey/gocovmerge - - - run: go test -v -race -db="sqlite3" -conn_str="./test.db" -coverprofile=coverage1-1.txt -covermode=atomic - - run: go test -v -race -db="sqlite3" -conn_str="./test.db" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic - - run: go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -coverprofile=coverage2-1.txt -covermode=atomic - - run: go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic - - run: go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -coverprofile=coverage3-1.txt -covermode=atomic - - run: go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic - - run: go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -coverprofile=coverage4-1.txt -covermode=atomic - - run: go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic - - run: go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic - - run: go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic - - run: go test -v -race -db="mssql" -conn_str="server=localhost;user id=sa;password=yourStrong(!)Password;database=xorm_test" -coverprofile=coverage6-1.txt -covermode=atomic - - run: go test -v -race -db="mssql" -conn_str="server=localhost;user id=sa;password=yourStrong(!)Password;database=xorm_test" -cache=true -coverprofile=coverage6-2.txt -covermode=atomic - - run: go test -v -race -db="mysql" -conn_str="root:@tcp(localhost:4000)/xorm_test" -ignore_select_update=true -coverprofile=coverage7-1.txt -covermode=atomic - - run: go test -v -race -db="mysql" -conn_str="root:@tcp(localhost:4000)/xorm_test" -ignore_select_update=true -cache=true -coverprofile=coverage7-2.txt -covermode=atomic - - run: gocovmerge coverage1-1.txt coverage1-2.txt coverage2-1.txt coverage2-2.txt coverage3-1.txt coverage3-2.txt coverage4-1.txt coverage4-2.txt coverage5-1.txt coverage5-2.txt coverage6-1.txt coverage6-2.txt coverage7-1.txt coverage7-2.txt > coverage.txt - - - run: bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index a4fb812d..65845d8c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,7 +3,6 @@ kind: pipeline name: testing steps: - name: test-vet - pull: default image: golang:1.12 environment: GO111MODULE: "on" @@ -16,44 +15,106 @@ steps: - pull_request - name: test-sqlite - pull: default image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_CACHE_ENABLE: false commands: - - "go test -v -race -db=\"sqlite3\" -conn_str=\"./test.db\" -coverprofile=coverage1-1.txt -covermode=atomic" - - "go test -v -race -db=\"sqlite3\" -conn_str=\"./test.db\" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic" + - make test-sqlite + when: + event: + - push + - pull_request + +- name: test-sqlite-cache + image: golang:1.12 + depends_on: + - test-sqlite + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_CACHE_ENABLE: true + commands: + - make test-sqlite when: event: - push - pull_request - name: test-mysql - pull: default image: golang:1.12 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_MYSQL_HOST: mysql + TEST_MYSQL_CHARSET: utf8 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + TEST_CACHE_ENABLE: false commands: - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test\" -coverprofile=coverage2-1.txt -covermode=atomic" - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test\" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic" + - make test-mysql when: event: - push - pull_request -- name: test-mysql-utf8mb4 - pull: default +- name: test-mysql-cache image: golang:1.12 depends_on: - test-mysql environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_MYSQL_HOST: mysql + TEST_MYSQL_CHARSET: utf8 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + TEST_CACHE_ENABLE: true commands: - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test?charset=utf8mb4\" -coverprofile=coverage2.1-1.txt -covermode=atomic" - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test?charset=utf8mb4\" -cache=true -coverprofile=coverage2.1-2.txt -covermode=atomic" + - make test-mysql + when: + event: + - push + - pull_request + +- name: test-mysql-utf8mb4 + image: golang:1.12 + depends_on: + - test-mysql-cache + 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: + TEST_CACHE_ENABLE: false + commands: + - make test-mysql + when: + event: + - push + - pull_request + +- name: test-mysql-utf8mb4-cache + image: golang:1.12 + depends_on: + - test-mysql-utf8mb4 + 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: + TEST_CACHE_ENABLE: true + commands: + - make test-mysql when: event: - push @@ -63,13 +124,37 @@ steps: pull: default image: golang:1.12 depends_on: - - test-mysql-utf8mb4 + - test-mysql-utf8mb4-cache environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_MYSQL_HOST: mysql:3306 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + TEST_CACHE_ENABLE: false commands: - - "go test -v -race -db=\"mymysql\" -conn_str=\"tcp:mysql:3306*xorm_test/root/\" -coverprofile=coverage3-1.txt -covermode=atomic" - - "go test -v -race -db=\"mymysql\" -conn_str=\"tcp:mysql:3306*xorm_test/root/\" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic" + - make test-mymysql + when: + event: + - push + - pull_request + +- name: test-mymysql-cache + pull: default + image: golang:1.12 + depends_on: + - test-mymysql + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_MYSQL_HOST: mysql:3306 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + TEST_CACHE_ENABLE: true + commands: + - make test-mymysql when: event: - push @@ -81,9 +166,33 @@ steps: environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + TEST_CACHE_ENABLE: false commands: - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -coverprofile=coverage4-1.txt -covermode=atomic" - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic" + - make test-postgres + when: + event: + - push + - pull_request + +- name: test-postgres-cache + pull: default + image: golang:1.12 + depends_on: + - test-postgres + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + TEST_CACHE_ENABLE: true + commands: + - make test-postgres when: event: - push @@ -95,9 +204,37 @@ steps: environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_SCHEMA: xorm + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + TEST_CACHE_ENABLE: false + SCHEMA: xorm commands: - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic" - - "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:postgres@pgsql/xorm_test?sslmode=disable\" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic" + - make test-postgres + when: + event: + - push + - pull_request + +- name: test-postgres-schema-cache + pull: default + image: golang:1.12 + depends_on: + - test-postgres-schema + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_SCHEMA: xorm + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + TEST_CACHE_ENABLE: true + SCHEMA: xorm + commands: + - make test-postgres when: event: - push @@ -109,9 +246,33 @@ steps: environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_MSSQL_HOST: mssql + TEST_MSSQL_DBNAME: xorm_test + TEST_MSSQL_USERNAME: sa + TEST_MSSQL_PASSWORD: "yourStrong(!)Password" + TEST_CACHE_ENABLE: false commands: - - "go test -v -race -db=\"mssql\" -conn_str=\"server=mssql;user id=sa;password=yourStrong(!)Password;database=xorm_test\" -coverprofile=coverage6-1.txt -covermode=atomic" - - "go test -v -race -db=\"mssql\" -conn_str=\"server=mssql;user id=sa;password=yourStrong(!)Password;database=xorm_test\" -cache=true -coverprofile=coverage6-2.txt -covermode=atomic" + - make test-mssql + when: + event: + - push + - pull_request + +- name: test-mssql-cache + pull: default + image: golang:1.12 + depends_on: + - test-mssql + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_MSSQL_HOST: mssql + TEST_MSSQL_DBNAME: xorm_test + TEST_MSSQL_USERNAME: sa + TEST_MSSQL_PASSWORD: "yourStrong(!)Password" + TEST_CACHE_ENABLE: true + commands: + - make test-mssql when: event: - push @@ -123,9 +284,33 @@ steps: environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" + TEST_TIDB_HOST: "tidb:4000" + TEST_TIDB_DBNAME: xorm_test + TEST_TIDB_USERNAME: root + TEST_TIDB_PASSWORD: + TEST_CACHE_ENABLE: false commands: - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(tidb:4000)/xorm_test\" -ignore_select_update=true -coverprofile=coverage7-1.txt -covermode=atomic" - - "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(tidb:4000)/xorm_test\" -ignore_select_update=true -cache=true -coverprofile=coverage7-2.txt -covermode=atomic" + - make test-tidb + when: + event: + - push + - pull_request + +- name: test-tidb-cache + pull: default + image: golang:1.12 + depends_on: + - test-tidb + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_TIDB_HOST: "tidb:4000" + TEST_TIDB_DBNAME: xorm_test + TEST_TIDB_USERNAME: root + TEST_TIDB_PASSWORD: + TEST_CACHE_ENABLE: true + commands: + - make test-tidb when: event: - push @@ -140,16 +325,23 @@ steps: depends_on: - test-vet - test-sqlite + - test-sqlite-cache - test-mysql + - test-mysql-cache - test-mysql-utf8mb4 + - test-mysql-utf8mb4-cache - test-mymysql + - test-mymysql-cache - test-postgres + - test-postgres-cache - test-postgres-schema + - test-postgres-schema-cache - test-mssql + - test-mssql-cache - test-tidb + - test-tidb-cache commands: - - go get github.com/wadey/gocovmerge - - gocovmerge coverage1-1.txt coverage1-2.txt coverage2-1.txt coverage2-2.txt coverage2.1-1.txt coverage2.1-2.txt coverage3-1.txt coverage3-2.txt coverage4-1.txt coverage4-2.txt coverage5-1.txt coverage5-2.txt coverage6-1.txt coverage6-2.txt coverage7-1.txt coverage7-2.txt > coverage.txt + - make coverage when: event: - push diff --git a/.revive.toml b/.revive.toml new file mode 100644 index 00000000..64e223bb --- /dev/null +++ b/.revive.toml @@ -0,0 +1,25 @@ +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.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +[rule.indent-error-flow] +[rule.errorf] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..d6ce2332 --- /dev/null +++ b/Makefile @@ -0,0 +1,190 @@ +IMPORT := xorm.io/xorm +export GO111MODULE=on + +GO ?= go +GOFMT ?= gofmt -s +TAGS ?= +SED_INPLACE := sed -i + +GOFILES := $(shell find . -name "*.go" -type f) + +PACKAGES ?= $(shell GO111MODULE=on $(GO) list ./...) + +TEST_MSSQL_HOST ?= mssql:1433 +TEST_MSSQL_DBNAME ?= gitea +TEST_MSSQL_USERNAME ?= sa +TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 + +TEST_MYSQL_HOST ?= mysql:3306 +TEST_MYSQL_CHARSET ?= utf8 +TEST_MYSQL_DBNAME ?= xorm_test +TEST_MYSQL_USERNAME ?= root +TEST_MYSQL_PASSWORD ?= + +TEST_PGSQL_HOST ?= pgsql:5432 +TEST_PGSQL_SCHEMA ?= +TEST_PGSQL_DBNAME ?= xorm_test +TEST_PGSQL_USERNAME ?= postgres +TEST_PGSQL_PASSWORD ?= mysecretpassword + +TEST_TIDB_HOST ?= tidb:4000 +TEST_TIDB_DBNAME ?= xorm_test +TEST_TIDB_USERNAME ?= root +TEST_TIDB_PASSWORD ?= + +TEST_CACHE_ENABLE ?= false + +.PHONY: all +all: build + +.PHONY: build +build: go-check $(GO_SOURCES) + $(GO) build + +.PHONY: clean +clean: + $(GO) clean -i ./... + rm -rf *.sql *.log test.db *coverage.out coverage.all + +.PHONY: coverage +coverage: + @hash gocovmerge > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) get -u github.com/wadey/gocovmerge; \ + fi + gocovmerge $(shell find . -type f -name "coverage.out") > coverage.all;\ + +.PHONY: fmt +fmt: + $(GOFMT) -w $(GOFILES) + +.PHONY: fmt-check +fmt-check: + # get all go files and run go fmt on them + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +.PHONY: go-check +go-check: + $(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell go version | grep -Eo '[0-9]+\.?[0-9]+?\.?[0-9]?\s' | tr '.' ' ');)) + @if [ "$(GO_VERSION)" -lt "001011000" ]; then \ + echo "Gitea requires Go 1.11.0 or greater to build. You can get it at https://golang.org/dl/"; \ + exit 1; \ + fi + +.PHONY: help +help: + @echo "Make Routines:" + @echo " - equivalent to \"build\"" + @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 " - misspell check if a word is written wrong" + @echo " - test run default unit test" + @echo " - test-sqlite run unit test for sqlite" + @echo " - vet examines Go source code and reports suspicious constructs" + +.PHONY: lint +lint: revive + +.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: test-sqlite + +.PNONY: test-mssql +test-mssql: go-check + $(GO) test -race -db=mssql -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ + -coverprofile=mssql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-mssql\#% +test-mssql\#%: go-check + $(GO) test -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ + -coverprofile=mssql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-mymysql +test-mymysql: go-check + $(GO) test -race -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="tcp:$(TEST_MYSQL_HOST)*$(TEST_MYSQL_DBNAME)/$(TEST_MYSQL_USERNAME)/$(TEST_MYSQL_PASSWORD)" \ + -coverprofile=mymysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-mymysql\#% +test-mymysql\#%: go-check + $(GO) test -race -run $* -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="tcp:$(TEST_MYSQL_HOST)*$(TEST_MYSQL_DBNAME)/$(TEST_MYSQL_USERNAME)/$(TEST_MYSQL_PASSWORD)" \ + -coverprofile=mymysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-mysql +test-mysql: go-check + $(GO) test -race -db=mysql -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \ + -coverprofile=mysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-mysql\#% +test-mysql\#%: go-check + $(GO) test -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \ + -coverprofile=mysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-postgres +test-postgres: go-check + $(GO) test -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" \ + -coverprofile=postgres.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-postgres\#% +test-postgres\#%: go-check + $(GO) test -race -run $* -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" \ + -coverprofile=postgres.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-sqlite +test-sqlite: go-check + $(GO) test -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -coverprofile=sqlite.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-sqlite\#% +test-sqlite\#%: go-check + $(GO) test -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -coverprofile=sqlite.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-tidb +test-tidb: go-check + $(GO) test -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)" \ + -coverprofile=tidb.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-tidb\#% +test-tidb\#%: go-check + $(GO) test -race -run $* -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)" \ + -coverprofile=tidb.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: vet +vet: + $(GO) vet $(PACKAGES) \ No newline at end of file diff --git a/cache_lru_test.go b/cache_lru_test.go index 7da36f00..06373f79 100644 --- a/cache_lru_test.go +++ b/cache_lru_test.go @@ -7,8 +7,8 @@ package xorm import ( "testing" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) func TestLRUCache(t *testing.T) { diff --git a/cache_memory_store_test.go b/cache_memory_store_test.go index fc27ae32..8e267683 100644 --- a/cache_memory_store_test.go +++ b/cache_memory_store_test.go @@ -25,12 +25,12 @@ func TestMemoryStore(t *testing.T) { assert.EqualValues(t, v, val) } - for k, _ := range kvs { + for k := range kvs { err := store.Del(k) assert.NoError(t, err) } - for k, _ := range kvs { + for k := range kvs { _, err := store.Get(k) assert.EqualValues(t, ErrNotExist, err) } diff --git a/engine.go b/engine.go index b76d4c6d..21b0ec05 100644 --- a/engine.go +++ b/engine.go @@ -838,7 +838,7 @@ func (engine *Engine) Having(conditions string) *Session { return session.Having(conditions) } -// UnMapType removes the datbase mapper of a type +// UnMapType removes the database mapper of a type func (engine *Engine) UnMapType(t reflect.Type) { engine.mutex.Lock() defer engine.mutex.Unlock() @@ -987,11 +987,11 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { pStart := strings.Index(k, "(") if pStart == 0 { - return nil, errors.New("( could not be the first charactor") + 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 ) charactor", col.FieldName, key) + return nil, fmt.Errorf("field %s tag %s cannot match ) character", col.FieldName, key) } ctx.tagName = k[:pStart] diff --git a/examples/cache.go b/examples/cache/cache.go similarity index 100% rename from examples/cache.go rename to examples/cache/cache.go index 5ad1de1b..ba756813 100644 --- a/examples/cache.go +++ b/examples/cache/cache.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // User describes a user diff --git a/examples/cachegoroutine.go b/examples/cache_gorountine/cache_goroutine.go similarity index 100% rename from examples/cachegoroutine.go rename to examples/cache_gorountine/cache_goroutine.go index c8d8ee69..94b29e82 100644 --- a/examples/cachegoroutine.go +++ b/examples/cache_gorountine/cache_goroutine.go @@ -6,8 +6,8 @@ import ( "time" _ "github.com/go-sql-driver/mysql" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // User describes a user diff --git a/examples/conversion.go b/examples/conversion/conversion.go similarity index 100% rename from examples/conversion.go rename to examples/conversion/conversion.go index 62d4a86b..9b16fdc1 100644 --- a/examples/conversion.go +++ b/examples/conversion/conversion.go @@ -5,8 +5,8 @@ import ( "fmt" "os" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // Status describes a status diff --git a/examples/derive.go b/examples/derive/derive.go similarity index 100% rename from examples/derive.go rename to examples/derive/derive.go index 23e7f169..ec6fe093 100644 --- a/examples/derive.go +++ b/examples/derive/derive.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // User describes a user diff --git a/examples/find.go b/examples/find/find.go similarity index 94% rename from examples/find.go rename to examples/find/find.go index ae27a797..9414650e 100644 --- a/examples/find.go +++ b/examples/find/find.go @@ -5,8 +5,8 @@ import ( "os" "time" + _ "github.com/mattn/go-sqlite3" "xorm.io/xorm" - _ "github.com/mattn/go-sqlite3" ) // User describes a user diff --git a/examples/goroutine.go b/examples/goroutine/goroutine.go similarity index 100% rename from examples/goroutine.go rename to examples/goroutine/goroutine.go index d320714a..57cb20a4 100644 --- a/examples/goroutine.go +++ b/examples/goroutine/goroutine.go @@ -6,8 +6,8 @@ import ( "runtime" _ "github.com/go-sql-driver/mysql" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // User describes a user diff --git a/examples/singlemapping.go b/examples/mapping/single_mapping.go similarity index 100% rename from examples/singlemapping.go rename to examples/mapping/single_mapping.go index 5c61448b..28a776d0 100644 --- a/examples/singlemapping.go +++ b/examples/mapping/single_mapping.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // User describes a user diff --git a/examples/maxconnect.go b/examples/max_connect/max_connect.go similarity index 100% rename from examples/maxconnect.go rename to examples/max_connect/max_connect.go index d8d8b0d8..42e9ce6d 100644 --- a/examples/maxconnect.go +++ b/examples/max_connect/max_connect.go @@ -6,8 +6,8 @@ import ( "runtime" _ "github.com/go-sql-driver/mysql" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // User describes a user diff --git a/examples/sync.go b/examples/sync/sync.go similarity index 100% rename from examples/sync.go rename to examples/sync/sync.go index 92647c0f..96aa8ae7 100644 --- a/examples/sync.go +++ b/examples/sync/sync.go @@ -4,9 +4,9 @@ import ( "fmt" _ "github.com/go-sql-driver/mysql" - "xorm.io/xorm" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) // SyncUser2 describes a user diff --git a/examples/tables.go b/examples/tables/tables.go similarity index 100% rename from examples/tables.go rename to examples/tables/tables.go index fcf49219..eb61c795 100644 --- a/examples/tables.go +++ b/examples/tables/tables.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm" ) func main() { diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 3a52787c..19554f7e 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -6,9 +6,9 @@ import ( "os" "testing" - "xorm.io/xorm" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" + "xorm.io/xorm" ) type Person struct { diff --git a/session_cond_test.go b/session_cond_test.go index 10650484..865890d0 100644 --- a/session_cond_test.go +++ b/session_cond_test.go @@ -9,8 +9,8 @@ import ( "fmt" "testing" - "xorm.io/builder" "github.com/stretchr/testify/assert" + "xorm.io/builder" ) func TestBuilder(t *testing.T) { diff --git a/session_delete_test.go b/session_delete_test.go index 5edb0718..ca0402fd 100644 --- a/session_delete_test.go +++ b/session_delete_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) func TestDelete(t *testing.T) { diff --git a/session_find_test.go b/session_find_test.go index f805f06e..9cb6ec07 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) func TestJoinLimit(t *testing.T) { diff --git a/session_insert_test.go b/session_insert_test.go index 9cd5b86a..6190d18a 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -553,10 +553,10 @@ func TestInsertMulti2(t *testing.T) { assert.EqualValues(t, len(users), cnt) users2 := []*Userinfo{ - &Userinfo{Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &Userinfo{Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - &Userinfo{Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &Userinfo{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + {Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + {Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, } cnt, err = testEngine.Insert(&users2) @@ -646,10 +646,10 @@ func TestInsertMulti3(t *testing.T) { assert.EqualValues(t, len(users), cnt) users2 := []*MyUserinfo{ - &MyUserinfo{Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &MyUserinfo{Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - &MyUserinfo{Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &MyUserinfo{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + {Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + {Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, } cnt, err = testEngine.Insert(&users2) @@ -691,10 +691,10 @@ func TestInsertMulti4(t *testing.T) { assert.EqualValues(t, len(users), cnt) users2 := []*MyUserinfo2{ - &MyUserinfo2{Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &MyUserinfo2{Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, - &MyUserinfo2{Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, - &MyUserinfo2{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + {Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + {Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, } cnt, err = testEngine.Insert(&users2) @@ -1083,7 +1083,7 @@ func TestInsertTwice(t *testing.T) { var sliceA []InsertStructA // sliceA is empty sliceB := []InsertStructB{ - InsertStructB{ + { FieldB: 1, }, } diff --git a/session_pk_test.go b/session_pk_test.go index 4c066634..2f54b5bf 100644 --- a/session_pk_test.go +++ b/session_pk_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) type IntId struct { diff --git a/session_stats_test.go b/session_stats_test.go index 01c76ba5..d66a7e1f 100644 --- a/session_stats_test.go +++ b/session_stats_test.go @@ -9,8 +9,8 @@ import ( "strconv" "testing" - "xorm.io/builder" "github.com/stretchr/testify/assert" + "xorm.io/builder" ) func isFloatEq(i, j float64, precision int) bool { diff --git a/session_tx_test.go b/session_tx_test.go index 23e1bf28..7c66b7c7 100644 --- a/session_tx_test.go +++ b/session_tx_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) func TestTransaction(t *testing.T) { diff --git a/session_update_test.go b/session_update_test.go index 274a6bfa..d646ff2d 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -407,7 +407,7 @@ func TestUpdate1(t *testing.T) { } if cnt != 1 { - err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt)) + err = fmt.Errorf("insert not returned 1 but %d", cnt) t.Error(err) panic(err) } @@ -745,7 +745,7 @@ func TestUpdateSameMapper(t *testing.T) { assert.NoError(t, err) if cnt != 1 { - err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt)) + err = fmt.Errorf("insert not returned 1 but %d", cnt) t.Error(err) panic(err) } @@ -861,7 +861,7 @@ func TestUseBool(t *testing.T) { var fNumber int64 for _, u := range users { if u.IsMan == false { - fNumber += 1 + fNumber++ } } diff --git a/tag.go b/tag.go index ec8d5cf0..2add6485 100644 --- a/tag.go +++ b/tag.go @@ -81,7 +81,7 @@ func OnlyToDBTagHandler(ctx *tagContext) error { return nil } -// PKTagHandler decribes primary key tag handler +// PKTagHandler describes primary key tag handler func PKTagHandler(ctx *tagContext) error { ctx.col.IsPrimaryKey = true ctx.col.Nullable = false diff --git a/tag_extends_test.go b/tag_extends_test.go index 5a8031f0..b23b8181 100644 --- a/tag_extends_test.go +++ b/tag_extends_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) type tempUser struct { diff --git a/tag_version_test.go b/tag_version_test.go index cd6dc935..878737c1 100644 --- a/tag_version_test.go +++ b/tag_version_test.go @@ -56,7 +56,7 @@ func TestVersion1(t *testing.T) { } if !has { - t.Error(errors.New(fmt.Sprintf("no version id is %v", ver.Id))) + t.Error(fmt.Errorf("no version id is %v", ver.Id)) panic(err) } fmt.Println(newVer) @@ -170,7 +170,7 @@ func TestVersion3(t *testing.T) { } if !has { - t.Error(errors.New(fmt.Sprintf("no version id is %v", ver.Id))) + t.Error(fmt.Errorf("no version id is %v", ver.Id)) panic(err) } fmt.Println(newVer) diff --git a/test_mssql.sh b/test_mssql.sh deleted file mode 100755 index 7f060cff..00000000 --- a/test_mssql.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mssql -conn_str="server=localhost;user id=sa;password=yourStrong(!)Password;database=xorm_test" \ No newline at end of file diff --git a/test_mssql_cache.sh b/test_mssql_cache.sh deleted file mode 100755 index 76efd6ca..00000000 --- a/test_mssql_cache.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mssql -conn_str="server=192.168.1.58;user id=sa;password=123456;database=xorm_test" -cache=true \ No newline at end of file diff --git a/test_mymysql.sh b/test_mymysql.sh deleted file mode 100755 index f7780d14..00000000 --- a/test_mymysql.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mymysql -conn_str="xorm_test/root/" \ No newline at end of file diff --git a/test_mymysql_cache.sh b/test_mymysql_cache.sh deleted file mode 100755 index 0100286d..00000000 --- a/test_mymysql_cache.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mymysql -conn_str="xorm_test/root/" -cache=true \ No newline at end of file diff --git a/test_mysql.sh b/test_mysql.sh deleted file mode 100755 index 650e4ee1..00000000 --- a/test_mysql.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mysql -conn_str="root:@/xorm_test" \ No newline at end of file diff --git a/test_mysql_cache.sh b/test_mysql_cache.sh deleted file mode 100755 index c542e735..00000000 --- a/test_mysql_cache.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mysql -conn_str="root:@/xorm_test" -cache=true \ No newline at end of file diff --git a/test_postgres.sh b/test_postgres.sh deleted file mode 100755 index dc1152e0..00000000 --- a/test_postgres.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=postgres -conn_str="dbname=xorm_test sslmode=disable" \ No newline at end of file diff --git a/test_postgres_cache.sh b/test_postgres_cache.sh deleted file mode 100755 index 462fc948..00000000 --- a/test_postgres_cache.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=postgres -conn_str="dbname=xorm_test sslmode=disable" -cache=true \ No newline at end of file diff --git a/test_sqlite.sh b/test_sqlite.sh deleted file mode 100755 index 6352b5cb..00000000 --- a/test_sqlite.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ No newline at end of file diff --git a/test_sqlite_cache.sh b/test_sqlite_cache.sh deleted file mode 100755 index 75a054c3..00000000 --- a/test_sqlite_cache.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" -cache=true \ No newline at end of file diff --git a/test_tidb.sh b/test_tidb.sh deleted file mode 100755 index 03d2d6cd..00000000 --- a/test_tidb.sh +++ /dev/null @@ -1 +0,0 @@ -go test -db=mysql -conn_str="root:@tcp(localhost:4000)/xorm_test" -ignore_select_update=true \ No newline at end of file diff --git a/types_test.go b/types_test.go index 274609b2..c979ded4 100644 --- a/types_test.go +++ b/types_test.go @@ -9,8 +9,8 @@ import ( "fmt" "testing" - "xorm.io/core" "github.com/stretchr/testify/assert" + "xorm.io/core" ) func TestArrayField(t *testing.T) { @@ -267,14 +267,14 @@ type Status struct { } var ( - _ core.Conversion = &Status{} - Registed Status = Status{"Registed", "white"} - Approved Status = Status{"Approved", "green"} - Removed Status = Status{"Removed", "red"} - Statuses map[string]Status = map[string]Status{ - Registed.Name: Registed, - Approved.Name: Approved, - Removed.Name: Removed, + _ core.Conversion = &Status{} + Registered Status = Status{"Registered", "white"} + Approved Status = Status{"Approved", "green"} + Removed Status = Status{"Removed", "red"} + Statuses map[string]Status = map[string]Status{ + Registered.Name: Registered, + Approved.Name: Approved, + Removed.Name: Removed, } ) @@ -318,7 +318,7 @@ func TestCustomType2(t *testing.T) { assert.NoError(t, err) } - cnt, err := session.Insert(&UserCus{1, "xlw", Registed}) + cnt, err := session.Insert(&UserCus{1, "xlw", Registered}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) @@ -335,7 +335,7 @@ func TestCustomType2(t *testing.T) { fmt.Println(user) users := make([]UserCus, 0) - err = testEngine.Where("`"+testEngine.GetColumnMapper().Obj2Table("Status")+"` = ?", "Registed").Find(&users) + err = testEngine.Where("`"+testEngine.GetColumnMapper().Obj2Table("Status")+"` = ?", "Registered").Find(&users) assert.NoError(t, err) assert.EqualValues(t, 1, len(users)) From 9251a5165d649ee642e741d801d5d1734c3a1538 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 02:43:58 +0000 Subject: [PATCH 019/112] Add test for join subquery (#1528) Fix test Fix subquery with schema Add test for join subquery Add makefile (#1531) Fix drone Fix ci Add deps Improve drone Fix envs Add makefile Reviewed-on: https://gitea.com/xorm/xorm/pulls/1531 Add password for postgres drone image (#1530) Add password for postgres drone image Reviewed-on: https://gitea.com/xorm/xorm/pulls/1530 format time when sqlTypeName is core.Varchar (#1026) fix time test add test for time format sign codes according to contributing rules. format time when sqlTypeName is core.Varchar. Same with core.DateTime or core.TimeStamp Add test for second insert error (#1527) Add test for second insert error Reviewed-on: https://gitea.com/xorm/xorm/pulls/1527 Add tests for table name (#1517) add tests for table name Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix wrong warning log on autoincrement column when sync table (#1525) improve doc Fix wrong warning log on autoincrement column when sync table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1525 Fixed Join strings on func Exist (#1520) fix test fixed Join strings on func Exist Co-authored-by: Tomofumi Kusana Reviewed-on: https://gitea.com/xorm/xorm/pulls/1520 For nullable columns, store nil values as NULL (#531) Merge branch 'master' into jcsalem/fix/nil_ptr_is_nullable fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 For nullable columns, store nil values as NULL fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple... Reviewed-on: https://gitea.com/xorm/xorm/pulls/1528 --- engine_table.go | 12 ++++++++++-- session_query_test.go | 10 +++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/engine_table.go b/engine_table.go index 87388a35..001d72e7 100644 --- a/engine_table.go +++ b/engine_table.go @@ -25,13 +25,21 @@ func (engine *Engine) tbNameWithSchema(v string) string { return v } +func isSubQuery(tbName string) bool { + const selStr = "select" + if len(tbName) <= len(selStr)+1 { + return false + } + + return strings.EqualFold(tbName[:len(selStr)], selStr) || strings.EqualFold(tbName[:len(selStr)+1], "("+selStr) +} + // TableName returns table name with schema prefix if has func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string { tbName := engine.tbNameNoSchema(bean) - if len(includeSchema) > 0 && includeSchema[0] { + if len(includeSchema) > 0 && includeSchema[0] && !isSubQuery(tbName) { tbName = engine.tbNameWithSchema(tbName) } - return tbName } diff --git a/session_query_test.go b/session_query_test.go index 772206a8..7e72357f 100644 --- a/session_query_test.go +++ b/session_query_test.go @@ -371,10 +371,18 @@ func TestJoinWithSubQuery(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + tbName := testEngine.Quote(testEngine.TableName("join_with_sub_query_depart", true)) var querys []JoinWithSubQuery1 - err = testEngine.Join("INNER", builder.Select("id").From(testEngine.Quote(testEngine.TableName("join_with_sub_query_depart", true))), + 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"). + Find(&querys) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(querys)) + assert.EqualValues(t, q, querys[0]) } From ea2dea3897c1fe311f7643ce6ddcaabb04cd6b00 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 05:18:08 +0000 Subject: [PATCH 020/112] Fix slice of struct not cache bug (#895) Fix failure caused by nil bean Judge both type of struct and pointer in case of out-of-range Fix issue #894 Add test for join subquery (#1528) Fix test Fix subquery with schema Add test for join subquery Add makefile (#1531) Fix drone Fix ci Add deps Improve drone Fix envs Add makefile Reviewed-on: https://gitea.com/xorm/xorm/pulls/1531 Add password for postgres drone image (#1530) Add password for postgres drone image Reviewed-on: https://gitea.com/xorm/xorm/pulls/1530 format time when sqlTypeName is core.Varchar (#1026) fix time test add test for time format sign codes according to contributing rules. format time when sqlTypeName is core.Varchar. Same with core.DateTime or core.TimeStamp Add test for second insert error (#1527) Add test for second insert error Reviewed-on: https://gitea.com/xorm/xorm/pulls/1527 Add tests for table name (#1517) add tests for table name Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix wrong warning log on autoincrement column when sync table (#1525) improve doc Fix wrong warning log on autoincrement column when sync table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1525 Fixed Join strings on func Exist (#1520) fix test fixed Join strings on func Exist Co-authored-by: Tomofumi Kusana Reviewed-on: https://gitea.com/xorm/xorm/pulls/1520 For nullable columns, store nil values as NULL (#531) Merge branch 'master' into jcsalem/fix/nil_ptr_is_nullable fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 For nullable columns, store nil values as NULL fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) ... Co-authored-by: shanyy Co-authored-by: kusana Co-authored-by: Guillermo Prandi Co-authored-by: Guillermo Prandi Co-authored-by: yudppp Reviewed-on: https://gitea.com/xorm/xorm/pulls/895 --- session_find.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/session_find.go b/session_find.go index e16ae54c..f9dd24ca 100644 --- a/session_find.go +++ b/session_find.go @@ -396,7 +396,21 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in return err } bean := cacher.GetBean(tableName, sid) - if bean == nil || reflect.ValueOf(bean).Elem().Type() != t { + + // fix issue #894 + isHit := func() (ht bool) { + if bean == nil { + ht = false + return + } + ckb := reflect.ValueOf(bean).Elem().Type() + ht = ckb == t + if !ht && t.Kind() == reflect.Ptr { + ht = t.Elem() == ckb + } + return + } + if !isHit() { ides = append(ides, id) ididxes[sid] = idx } else { From 83ec880ee27341de16050ae287ea2564772cd625 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 06:40:24 +0000 Subject: [PATCH 021/112] Improve drone (#1537) Fix drone Improve drone * use traditional positional parameters on inser... Reviewed-on: https://gitea.com/xorm/xorm/pulls/1537 --- .drone.yml | 187 ++++------------------------------------------------- Makefile | 24 +++---- 2 files changed, 23 insertions(+), 188 deletions(-) diff --git a/.drone.yml b/.drone.yml index 65845d8c..0a535c6c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -19,24 +19,9 @@ steps: environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" - TEST_CACHE_ENABLE: false - commands: - - make test-sqlite - when: - event: - - push - - pull_request - -- name: test-sqlite-cache - image: golang:1.12 - depends_on: - - test-sqlite - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_CACHE_ENABLE: true commands: - make test-sqlite + - TEST_CACHE_ENABLE=true make test-sqlite when: event: - push @@ -52,29 +37,9 @@ steps: TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root TEST_MYSQL_PASSWORD: - TEST_CACHE_ENABLE: false - commands: - - make test-mysql - when: - event: - - push - - pull_request - -- name: test-mysql-cache - image: golang:1.12 - depends_on: - - test-mysql - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MYSQL_HOST: mysql - TEST_MYSQL_CHARSET: utf8 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - TEST_CACHE_ENABLE: true commands: - make test-mysql + - TEST_CACHE_ENABLE=true make test-mysql when: event: - push @@ -83,7 +48,7 @@ steps: - name: test-mysql-utf8mb4 image: golang:1.12 depends_on: - - test-mysql-cache + - test-mysql environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -92,29 +57,9 @@ steps: TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root TEST_MYSQL_PASSWORD: - TEST_CACHE_ENABLE: false - commands: - - make test-mysql - when: - event: - - push - - pull_request - -- name: test-mysql-utf8mb4-cache - image: golang:1.12 - depends_on: - - test-mysql-utf8mb4 - 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: - TEST_CACHE_ENABLE: true commands: - make test-mysql + - TEST_CACHE_ENABLE=true make test-mysql when: event: - push @@ -124,7 +69,7 @@ steps: pull: default image: golang:1.12 depends_on: - - test-mysql-utf8mb4-cache + - test-mysql-utf8mb4 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -132,29 +77,9 @@ steps: TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root TEST_MYSQL_PASSWORD: - TEST_CACHE_ENABLE: false - commands: - - make test-mymysql - when: - event: - - push - - pull_request - -- name: test-mymysql-cache - pull: default - image: golang:1.12 - depends_on: - - test-mymysql - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MYSQL_HOST: mysql:3306 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - TEST_CACHE_ENABLE: true commands: - make test-mymysql + - TEST_CACHE_ENABLE=true make test-mymysql when: event: - push @@ -170,59 +95,19 @@ steps: TEST_PGSQL_DBNAME: xorm_test TEST_PGSQL_USERNAME: postgres TEST_PGSQL_PASSWORD: postgres - TEST_CACHE_ENABLE: false - commands: - - make test-postgres - when: - event: - - push - - pull_request - -- name: test-postgres-cache - pull: default - image: golang:1.12 - depends_on: - - test-postgres - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_PGSQL_HOST: pgsql - TEST_PGSQL_DBNAME: xorm_test - TEST_PGSQL_USERNAME: postgres - TEST_PGSQL_PASSWORD: postgres - TEST_CACHE_ENABLE: true commands: - make test-postgres + - TEST_CACHE_ENABLE=true make test-postgres when: event: - push - pull_request - name: test-postgres-schema - pull: default - image: golang:1.12 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_PGSQL_HOST: pgsql - TEST_PGSQL_SCHEMA: xorm - TEST_PGSQL_DBNAME: xorm_test - TEST_PGSQL_USERNAME: postgres - TEST_PGSQL_PASSWORD: postgres - TEST_CACHE_ENABLE: false - SCHEMA: xorm - commands: - - make test-postgres - when: - event: - - push - - pull_request - -- name: test-postgres-schema-cache pull: default image: golang:1.12 depends_on: - - test-postgres-schema + - test-postgres environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" @@ -231,10 +116,9 @@ steps: TEST_PGSQL_DBNAME: xorm_test TEST_PGSQL_USERNAME: postgres TEST_PGSQL_PASSWORD: postgres - TEST_CACHE_ENABLE: true - SCHEMA: xorm commands: - make test-postgres + - TEST_CACHE_ENABLE=true make test-postgres when: event: - push @@ -250,29 +134,9 @@ steps: TEST_MSSQL_DBNAME: xorm_test TEST_MSSQL_USERNAME: sa TEST_MSSQL_PASSWORD: "yourStrong(!)Password" - TEST_CACHE_ENABLE: false - commands: - - make test-mssql - when: - event: - - push - - pull_request - -- name: test-mssql-cache - pull: default - image: golang:1.12 - depends_on: - - test-mssql - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MSSQL_HOST: mssql - TEST_MSSQL_DBNAME: xorm_test - TEST_MSSQL_USERNAME: sa - TEST_MSSQL_PASSWORD: "yourStrong(!)Password" - TEST_CACHE_ENABLE: true commands: - make test-mssql + - TEST_CACHE_ENABLE=true make test-mssql when: event: - push @@ -288,29 +152,9 @@ steps: TEST_TIDB_DBNAME: xorm_test TEST_TIDB_USERNAME: root TEST_TIDB_PASSWORD: - TEST_CACHE_ENABLE: false - commands: - - make test-tidb - when: - event: - - push - - pull_request - -- name: test-tidb-cache - pull: default - image: golang:1.12 - depends_on: - - test-tidb - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_TIDB_HOST: "tidb:4000" - TEST_TIDB_DBNAME: xorm_test - TEST_TIDB_USERNAME: root - TEST_TIDB_PASSWORD: - TEST_CACHE_ENABLE: true commands: - make test-tidb + - TEST_CACHE_ENABLE=true make test-tidb when: event: - push @@ -325,21 +169,12 @@ steps: depends_on: - test-vet - test-sqlite - - test-sqlite-cache - test-mysql - - test-mysql-cache - - test-mysql-utf8mb4 - - test-mysql-utf8mb4-cache - test-mymysql - - test-mymysql-cache - test-postgres - - test-postgres-cache - test-postgres-schema - - test-postgres-schema-cache - test-mssql - - test-mssql-cache - test-tidb - - test-tidb-cache commands: - make coverage when: diff --git a/Makefile b/Makefile index d6ce2332..737ca96c 100644 --- a/Makefile +++ b/Makefile @@ -117,71 +117,71 @@ test: test-sqlite .PNONY: test-mssql test-mssql: go-check - $(GO) test -race -db=mssql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ -coverprofile=mssql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mssql\#% test-mssql\#%: go-check - $(GO) test -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ -coverprofile=mssql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mymysql test-mymysql: go-check - $(GO) test -race -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ -conn_str="tcp:$(TEST_MYSQL_HOST)*$(TEST_MYSQL_DBNAME)/$(TEST_MYSQL_USERNAME)/$(TEST_MYSQL_PASSWORD)" \ -coverprofile=mymysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mymysql\#% test-mymysql\#%: go-check - $(GO) test -race -run $* -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -run $* -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ -conn_str="tcp:$(TEST_MYSQL_HOST)*$(TEST_MYSQL_DBNAME)/$(TEST_MYSQL_USERNAME)/$(TEST_MYSQL_PASSWORD)" \ -coverprofile=mymysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mysql test-mysql: go-check - $(GO) test -race -db=mysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) \ -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \ -coverprofile=mysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-mysql\#% test-mysql\#%: go-check - $(GO) test -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) \ -conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \ -coverprofile=mysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-postgres test-postgres: go-check - $(GO) test -race -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -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" \ -coverprofile=postgres.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-postgres\#% test-postgres\#%: go-check - $(GO) test -race -run $* -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -run $* -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" \ -coverprofile=postgres.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite test-sqlite: go-check - $(GO) test -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ -coverprofile=sqlite.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite\#% test-sqlite\#%: go-check - $(GO) test -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ -coverprofile=sqlite.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-tidb test-tidb: go-check - $(GO) test -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ + $(GO) test -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)" \ -coverprofile=tidb.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-tidb\#% test-tidb\#%: go-check - $(GO) test -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ + $(GO) test -v -race -run $* -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)" \ -coverprofile=tidb.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic From bc8bb23ad0dce1745a189a508aff75db7ef2fb61 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 08:15:36 +0000 Subject: [PATCH 022/112] Add test for join limit (#1536) Add test for join limit Reviewed-on: https://gitea.com/xorm/xorm/pulls/1536 --- session_find_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/session_find_test.go b/session_find_test.go index 9cb6ec07..a5c7ae4f 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -801,3 +801,24 @@ func TestFindJoin(t *testing.T) { Where("scene_item.type=?", 3).Or("device_user_privrels.user_id=?", 339).Find(&scenes) assert.NoError(t, err) } + +func TestJoinFindLimit(t *testing.T) { + type JoinFindLimit1 struct { + Id int64 + Name string + } + + type JoinFindLimit2 struct { + Id int64 + Eid int64 `xorm:"index"` + Name string + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(JoinFindLimit1), new(JoinFindLimit2)) + + var finds []JoinFindLimit1 + err := testEngine.Join("INNER", new(JoinFindLimit2), "join_find_limit2.eid=join_find_limit1.id"). + Limit(10, 10).Find(&finds) + assert.NoError(t, err) +} From 35b281379604827d2a5b6a9a06f32aeff493b155 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 10:07:08 +0000 Subject: [PATCH 023/112] Add test for mysql8.0 (#1538) Fix pk order on test Add test for mysql8.0 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1538 --- .drone.yml | 31 +++++++++++++++++++++++++++++++ session_pk_test.go | 7 +++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0a535c6c..7a273d3d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -45,6 +45,24 @@ steps: - push - pull_request +- name: test-mysql8 + image: golang:1.12 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_MYSQL_HOST: mysql8 + 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 + when: + event: + - push + - pull_request + - name: test-mysql-utf8mb4 image: golang:1.12 depends_on: @@ -170,6 +188,7 @@ steps: - test-vet - test-sqlite - test-mysql + - test-mysql8 - test-mymysql - test-postgres - test-postgres-schema @@ -196,6 +215,18 @@ services: - 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: pgsql pull: default image: postgres:9.5 diff --git a/session_pk_test.go b/session_pk_test.go index 2f54b5bf..039ab367 100644 --- a/session_pk_test.go +++ b/session_pk_test.go @@ -6,6 +6,7 @@ package xorm import ( "errors" + "sort" "testing" "time" @@ -1141,8 +1142,10 @@ func TestCompositePK(t *testing.T) { pkCols := table.PKColumns() assert.EqualValues(t, 2, len(pkCols)) - assert.EqualValues(t, "uid", pkCols[0].Name) - assert.EqualValues(t, "tid", pkCols[1].Name) + + names := []string{pkCols[0].Name, pkCols[1].Name} + sort.Strings(names) + assert.EqualValues(t, []string{"tid", "uid"}, names) } func TestNoPKIdQueryUpdate(t *testing.T) { From eabfb48b5deb858022017bfadd92a68736d054e5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 12:17:40 +0000 Subject: [PATCH 024/112] Fix int time deleted bug (#1539) Fix panic Fix test Fix test for mssql time Add sql type check on deleted cond Fix int time deleted bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1539 --- engine.go | 19 +++++++++++++++---- engine_cond.go | 2 +- session_find.go | 2 +- session_update.go | 2 +- time_test.go | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/engine.go b/engine.go index 21b0ec05..99e9b584 100644 --- a/engine.go +++ b/engine.go @@ -91,11 +91,22 @@ func (engine *Engine) BufferSize(size int) *Session { } // CondDeleted returns the conditions whether a record is soft deleted. -func (engine *Engine) CondDeleted(colName string) builder.Cond { - if engine.dialect.DBType() == core.MSSQL { - return builder.IsNull{colName} +func (engine *Engine) CondDeleted(col *core.Column) builder.Cond { + var cond = builder.NewCond() + if col.SQLType.IsNumeric() { + cond = builder.Eq{col.Name: 0} + } else { + // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. + if engine.dialect.DBType() != core.MSSQL { + cond = builder.Eq{col.Name: zeroTime1} + } } - return builder.IsNull{colName}.Or(builder.Eq{colName: zeroTime1}) + + if col.Nullable { + cond = cond.Or(builder.IsNull{col.Name}) + } + + return cond } // ShowSQL show SQL statement or not on logger if log level is great than INFO diff --git a/engine_cond.go b/engine_cond.go index 702ac804..17f50bd7 100644 --- a/engine_cond.go +++ b/engine_cond.go @@ -58,7 +58,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, } if col.IsDeleted && !unscoped { // tag "deleted" is enabled - conds = append(conds, engine.CondDeleted(colName)) + conds = append(conds, engine.CondDeleted(col)) } fieldValue := *fieldValuePtr diff --git a/session_find.go b/session_find.go index f9dd24ca..c7043ea6 100644 --- a/session_find.go +++ b/session_find.go @@ -121,7 +121,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) colName = session.engine.Quote(nm) + "." + colName } - autoCond = session.engine.CondDeleted(colName) + autoCond = session.engine.CondDeleted(col) } } } diff --git a/session_update.go b/session_update.go index 18425ec3..ceaade46 100644 --- a/session_update.go +++ b/session_update.go @@ -287,7 +287,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if !condBeanIsStruct && table != nil { if col := table.DeletedColumn(); col != nil && !session.statement.unscoped { // tag "deleted" is enabled - autoCond1 := session.engine.CondDeleted(session.engine.Quote(col.Name)) + autoCond1 := session.engine.CondDeleted(col) if autoCond == nil { autoCond = autoCond1 diff --git a/time_test.go b/time_test.go index 9a821983..6ee97fe1 100644 --- a/time_test.go +++ b/time_test.go @@ -477,3 +477,40 @@ func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { assert.EqualValues(t, formatTime(time.Time(user3.DeletedAt)), formatTime(time.Time(user4.DeletedAt))) fmt.Println("user3", user3.DeletedAt, user4.DeletedAt) } + +func TestDeletedInt64(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DeletedInt64Struct struct { + Id int64 + Deleted int64 `xorm:"deleted default(0) notnull"` // timestamp + } + + assertSync(t, new(DeletedInt64Struct)) + + var d1 DeletedInt64Struct + cnt, err := testEngine.Insert(&d1) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var d2 DeletedInt64Struct + has, err := testEngine.ID(d1.Id).Get(&d2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, d1, d2) + + cnt, err = testEngine.ID(d1.Id).NoAutoCondition().Delete(&d1) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var d3 DeletedInt64Struct + has, err = testEngine.ID(d1.Id).Get(&d3) + assert.NoError(t, err) + assert.False(t, has) + + var d4 DeletedInt64Struct + has, err = testEngine.ID(d1.Id).Unscoped().Get(&d4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, d1, d4) +} From bf25a77bca0721124db5a7c20b04fcbbf6127296 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 08:53:18 +0000 Subject: [PATCH 025/112] Merge core package back into the main repository and split into serval sub packages. (#1543) Fix test Improve fmt update go.mod Move core as a sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1543 --- .gitignore | 2 + cache_test.go | 8 +- caches/cache.go | 99 + cache_lru.go => caches/cache_lru.go | 20 +- cache_lru_test.go => caches/cache_lru_test.go | 6 +- .../cache_memory_store.go | 6 +- .../cache_memory_store_test.go | 2 +- convert.go | 7 + core/db.go | 229 ++ core/db_test.go | 684 +++++ core/error.go | 14 + core/rows.go | 338 +++ core/scan.go | 66 + core/stmt.go | 166 ++ core/tx.go | 153 ++ dialects/dialect.go | 410 +++ dialects/driver.go | 31 + dialects/filter.go | 95 + dialects/filter_test.go | 39 + dialect_mssql.go => dialects/mssql.go | 117 +- .../mssql_test.go | 10 +- dialect_mysql.go => dialects/mysql.go | 135 +- dialect_oracle.go => dialects/oracle.go | 103 +- dialect_postgres.go => dialects/postgres.go | 173 +- .../postgres_test.go | 19 +- dialect_sqlite3.go => dialects/sqlite3.go | 93 +- .../sqlite3_test.go | 2 +- doc.go | 2 +- docs/images/cache_design.graffle | 2295 ----------------- docs/images/cache_design.png | Bin 243596 -> 0 bytes engine.go | 175 +- engine_cond.go | 12 +- engine_group.go | 16 +- engine_table.go | 16 +- examples/cache/cache.go | 5 +- examples/cache_gorountine/cache_goroutine.go | 5 +- go.mod | 2 +- go.sum | 2 - helpers.go | 24 +- interface.go | 36 +- logger.go => log/logger.go | 98 +- syslogger.go => log/syslogger.go | 14 +- migrate/migrate.go | 4 +- names/mapper.go | 258 ++ names/mapper_test.go | 49 + table_name.go => names/table_name.go | 15 +- .../table_name_test.go | 54 +- rows.go | 2 +- schemas/column.go | 117 + schemas/index.go | 72 + schemas/pk.go | 30 + schemas/pk_test.go | 36 + schemas/table.go | 156 ++ schemas/table_test.go | 111 + schemas/type.go | 325 +++ session.go | 73 +- session_cols.go | 8 +- session_cols_test.go | 4 +- session_convert.go | 82 +- session_delete.go | 25 +- session_delete_test.go | 9 +- session_exist.go | 10 +- session_find.go | 25 +- session_find_test.go | 6 +- session_get.go | 13 +- session_get_test.go | 8 +- session_insert.go | 22 +- session_pk_test.go | 28 +- session_query.go | 7 +- session_query_test.go | 10 +- session_raw.go | 2 +- session_schema.go | 60 +- session_schema_test.go | 2 +- session_tx_test.go | 4 +- session_update.go | 25 +- session_update_test.go | 6 +- statement.go | 75 +- statement_args.go | 6 +- statement_test.go | 12 +- tag.go | 43 +- tag_extends_test.go | 14 +- tag_id_test.go | 6 +- tag_test.go | 4 +- types.go | 6 +- types_test.go | 12 +- xorm.go | 57 +- xorm_test.go | 34 +- 87 files changed, 4457 insertions(+), 3199 deletions(-) create mode 100644 caches/cache.go rename cache_lru.go => caches/cache_lru.go (93%) rename cache_lru_test.go => caches/cache_lru_test.go (94%) rename cache_memory_store.go => caches/cache_memory_store.go (93%) rename cache_memory_store_test.go => caches/cache_memory_store_test.go (97%) create mode 100644 core/db.go create mode 100644 core/db_test.go create mode 100644 core/error.go create mode 100644 core/rows.go create mode 100644 core/scan.go create mode 100644 core/stmt.go create mode 100644 core/tx.go create mode 100644 dialects/dialect.go create mode 100644 dialects/driver.go create mode 100644 dialects/filter.go create mode 100644 dialects/filter_test.go rename dialect_mssql.go => dialects/mssql.go (86%) rename dialect_mssql_test.go => dialects/mssql_test.go (84%) rename dialect_mysql.go => dialects/mysql.go (86%) rename dialect_oracle.go => dialects/oracle.go (90%) rename dialect_postgres.go => dialects/postgres.go (91%) rename dialect_postgres_test.go => dialects/postgres_test.go (92%) rename dialect_sqlite3.go => dialects/sqlite3.go (83%) rename dialect_sqlite3_test.go => dialects/sqlite3_test.go (97%) delete mode 100644 docs/images/cache_design.graffle delete mode 100644 docs/images/cache_design.png rename logger.go => log/logger.go (67%) rename syslogger.go => log/syslogger.go (88%) create mode 100644 names/mapper.go create mode 100644 names/mapper_test.go rename table_name.go => names/table_name.go (70%) rename table_name_test.go => names/table_name_test.go (55%) create mode 100644 schemas/column.go create mode 100644 schemas/index.go create mode 100644 schemas/pk.go create mode 100644 schemas/pk_test.go create mode 100644 schemas/table.go create mode 100644 schemas/table_test.go create mode 100644 schemas/type.go diff --git a/.gitignore b/.gitignore index 3629e49a..0d321a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ xorm.test test.db.sql .idea/ + +*coverage.out diff --git a/cache_test.go b/cache_test.go index 26d7ac68..1606d5bc 100644 --- a/cache_test.go +++ b/cache_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "xorm.io/xorm/caches" + "github.com/stretchr/testify/assert" ) @@ -21,7 +23,7 @@ func TestCacheFind(t *testing.T) { } oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) assert.NoError(t, testEngine.Sync2(new(MailBox))) @@ -96,7 +98,7 @@ func TestCacheFind2(t *testing.T) { } oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) assert.NoError(t, testEngine.Sync2(new(MailBox2))) @@ -147,7 +149,7 @@ func TestCacheGet(t *testing.T) { } oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) assert.NoError(t, testEngine.Sync2(new(MailBox3))) diff --git a/caches/cache.go b/caches/cache.go new file mode 100644 index 00000000..7b80eb88 --- /dev/null +++ b/caches/cache.go @@ -0,0 +1,99 @@ +// 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 caches + +import ( + "bytes" + "encoding/gob" + "errors" + "fmt" + "strings" + "time" + + "xorm.io/xorm/schemas" +) + +const ( + // CacheExpired is default cache expired time + CacheExpired = 60 * time.Minute + // CacheMaxMemory is not use now + CacheMaxMemory = 256 + // CacheGcInterval represents interval time to clear all expired nodes + CacheGcInterval = 10 * time.Minute + // CacheGcMaxRemoved represents max nodes removed when gc + CacheGcMaxRemoved = 20 +) + +// list all the errors +var ( + ErrCacheMiss = errors.New("xorm/cache: key not found") + ErrNotStored = errors.New("xorm/cache: not stored") + // ErrNotExist record does not exist error + ErrNotExist = errors.New("Record does not exist") +) + +// CacheStore is a interface to store cache +type CacheStore interface { + // key is primary key or composite primary key + // value is struct's pointer + // key format : -p--... + Put(key string, value interface{}) error + Get(key string) (interface{}, error) + Del(key string) error +} + +// Cacher is an interface to provide cache +// id format : u--... +type Cacher interface { + GetIds(tableName, sql string) interface{} + GetBean(tableName string, id string) interface{} + PutIds(tableName, sql string, ids interface{}) + PutBean(tableName string, id string, obj interface{}) + DelIds(tableName, sql string) + DelBean(tableName string, id string) + ClearIds(tableName string) + ClearBeans(tableName string) +} + +func encodeIds(ids []schemas.PK) (string, error) { + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(ids) + + return buf.String(), err +} + +func decodeIds(s string) ([]schemas.PK, error) { + pks := make([]schemas.PK, 0) + + dec := gob.NewDecoder(strings.NewReader(s)) + err := dec.Decode(&pks) + + return pks, err +} + +// GetCacheSql returns cacher PKs via SQL +func GetCacheSql(m Cacher, tableName, sql string, args interface{}) ([]schemas.PK, error) { + bytes := m.GetIds(tableName, GenSqlKey(sql, args)) + if bytes == nil { + return nil, errors.New("Not Exist") + } + return decodeIds(bytes.(string)) +} + +// PutCacheSql puts cacher SQL and PKs +func PutCacheSql(m Cacher, ids []schemas.PK, tableName, sql string, args interface{}) error { + bytes, err := encodeIds(ids) + if err != nil { + return err + } + m.PutIds(tableName, GenSqlKey(sql, args), bytes) + return nil +} + +// GenSqlKey generates cache key +func GenSqlKey(sql string, args interface{}) string { + return fmt.Sprintf("%v-%v", sql, args) +} diff --git a/cache_lru.go b/caches/cache_lru.go similarity index 93% rename from cache_lru.go rename to caches/cache_lru.go index ab948bd2..6b45ac94 100644 --- a/cache_lru.go +++ b/caches/cache_lru.go @@ -2,15 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "container/list" "fmt" "sync" "time" - - "xorm.io/core" ) // LRUCacher implments cache object facilities @@ -19,7 +17,7 @@ type LRUCacher struct { sqlList *list.List idIndex map[string]map[string]*list.Element sqlIndex map[string]map[string]*list.Element - store core.CacheStore + store CacheStore mutex sync.Mutex MaxElementSize int Expired time.Duration @@ -27,15 +25,15 @@ type LRUCacher struct { } // NewLRUCacher creates a cacher -func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher { +func NewLRUCacher(store CacheStore, maxElementSize int) *LRUCacher { return NewLRUCacher2(store, 3600*time.Second, maxElementSize) } // NewLRUCacher2 creates a cache include different params -func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher { +func NewLRUCacher2(store CacheStore, expired time.Duration, maxElementSize int) *LRUCacher { cacher := &LRUCacher{store: store, idList: list.New(), sqlList: list.New(), Expired: expired, - GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize, + GcInterval: CacheGcInterval, MaxElementSize: maxElementSize, sqlIndex: make(map[string]map[string]*list.Element), idIndex: make(map[string]map[string]*list.Element), } @@ -57,7 +55,7 @@ func (m *LRUCacher) GC() { defer m.mutex.Unlock() var removedNum int for e := m.idList.Front(); e != nil; { - if removedNum <= core.CacheGcMaxRemoved && + if removedNum <= CacheGcMaxRemoved && time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { removedNum++ next := e.Next() @@ -71,7 +69,7 @@ func (m *LRUCacher) GC() { removedNum = 0 for e := m.sqlList.Front(); e != nil; { - if removedNum <= core.CacheGcMaxRemoved && + if removedNum <= CacheGcMaxRemoved && time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { removedNum++ next := e.Next() @@ -268,11 +266,11 @@ type sqlNode struct { } func genSQLKey(sql string, args interface{}) string { - return fmt.Sprintf("%v-%v", sql, args) + return fmt.Sprintf("%s-%v", sql, args) } func genID(prefix string, id string) string { - return fmt.Sprintf("%v-%v", prefix, id) + return fmt.Sprintf("%s-%s", prefix, id) } func newIDNode(tbName string, id string) *idNode { diff --git a/cache_lru_test.go b/caches/cache_lru_test.go similarity index 94% rename from cache_lru_test.go rename to caches/cache_lru_test.go index 06373f79..771b924c 100644 --- a/cache_lru_test.go +++ b/caches/cache_lru_test.go @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestLRUCache(t *testing.T) { @@ -20,7 +20,7 @@ func TestLRUCache(t *testing.T) { cacher := NewLRUCacher(store, 10000) tableName := "cache_object1" - pks := []core.PK{ + pks := []schemas.PK{ {1}, {2}, } diff --git a/cache_memory_store.go b/caches/cache_memory_store.go similarity index 93% rename from cache_memory_store.go rename to caches/cache_memory_store.go index 0c483f45..f16254d8 100644 --- a/cache_memory_store.go +++ b/caches/cache_memory_store.go @@ -2,15 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "sync" - - "xorm.io/core" ) -var _ core.CacheStore = NewMemoryStore() +var _ CacheStore = NewMemoryStore() // MemoryStore represents in-memory store type MemoryStore struct { diff --git a/cache_memory_store_test.go b/caches/cache_memory_store_test.go similarity index 97% rename from cache_memory_store_test.go rename to caches/cache_memory_store_test.go index 8e267683..12db4ea7 100644 --- a/cache_memory_store_test.go +++ b/caches/cache_memory_store_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "testing" diff --git a/convert.go b/convert.go index 2316ca0b..b8e1c4fc 100644 --- a/convert.go +++ b/convert.go @@ -346,3 +346,10 @@ func asBool(bs []byte) (bool, error) { } return strconv.ParseBool(string(bs)) } + +// 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) +} diff --git a/core/db.go b/core/db.go new file mode 100644 index 00000000..8f16e848 --- /dev/null +++ b/core/db.go @@ -0,0 +1,229 @@ +// 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 core + +import ( + "context" + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + "regexp" + "sync" + + "xorm.io/xorm/names" +) + +var ( + // DefaultCacheSize sets the default cache size + DefaultCacheSize = 200 +) + +func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { + vv := reflect.ValueOf(mp) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return "", []interface{}{}, ErrNoMapPointer + } + + args := make([]interface{}, 0, len(vv.Elem().MapKeys())) + var err error + query = re.ReplaceAllStringFunc(query, func(src string) string { + v := vv.Elem().MapIndex(reflect.ValueOf(src[1:])) + if !v.IsValid() { + err = fmt.Errorf("map key %s is missing", src[1:]) + } else { + args = append(args, v.Interface()) + } + return "?" + }) + + return query, args, err +} + +func StructToSlice(query string, st interface{}) (string, []interface{}, error) { + vv := reflect.ValueOf(st) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return "", []interface{}{}, ErrNoStructPointer + } + + args := make([]interface{}, 0) + var err error + query = re.ReplaceAllStringFunc(query, func(src string) string { + fv := vv.Elem().FieldByName(src[1:]).Interface() + if v, ok := fv.(driver.Valuer); ok { + var value driver.Value + value, err = v.Value() + if err != nil { + return "?" + } + args = append(args, value) + } else { + args = append(args, fv) + } + return "?" + }) + if err != nil { + return "", []interface{}{}, err + } + return query, args, nil +} + +type cacheStruct struct { + value reflect.Value + idx int +} + +// DB is a wrap of sql.DB with extra contents +type DB struct { + *sql.DB + Mapper names.Mapper + reflectCache map[reflect.Type]*cacheStruct + reflectCacheMutex sync.RWMutex +} + +// Open opens a database +func Open(driverName, dataSourceName string) (*DB, error) { + db, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return &DB{ + DB: db, + Mapper: names.NewCacheMapper(&names.SnakeMapper{}), + reflectCache: make(map[reflect.Type]*cacheStruct), + }, nil +} + +// FromDB creates a DB from a sql.DB +func FromDB(db *sql.DB) *DB { + return &DB{ + DB: db, + Mapper: names.NewCacheMapper(&names.SnakeMapper{}), + reflectCache: make(map[reflect.Type]*cacheStruct), + } +} + +func (db *DB) reflectNew(typ reflect.Type) reflect.Value { + db.reflectCacheMutex.Lock() + defer db.reflectCacheMutex.Unlock() + cs, ok := db.reflectCache[typ] + if !ok || cs.idx+1 > DefaultCacheSize-1 { + cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0} + db.reflectCache[typ] = cs + } else { + cs.idx = cs.idx + 1 + } + return cs.value.Index(cs.idx).Addr() +} + +// QueryContext overwrites sql.DB.QueryContext +func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + rows, err := db.DB.QueryContext(ctx, query, args...) + if err != nil { + if rows != nil { + rows.Close() + } + return nil, err + } + return &Rows{rows, db}, nil +} + +// Query overwrites sql.DB.Query +func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { + return db.QueryContext(context.Background(), query, args...) +} + +// QueryMapContext executes query with parameters via map and context +func (db *DB) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return db.QueryContext(ctx, query, args...) +} + +// QueryMap executes query with parameters via map +func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { + return db.QueryMapContext(context.Background(), query, mp) +} + +func (db *DB) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return db.QueryContext(ctx, query, args...) +} + +func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { + return db.QueryStructContext(context.Background(), query, st) +} + +func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := db.QueryContext(ctx, query, args...) + if err != nil { + return &Row{nil, err} + } + return &Row{rows, nil} +} + +func (db *DB) QueryRow(query string, args ...interface{}) *Row { + return db.QueryRowContext(context.Background(), query, args...) +} + +func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { + query, args, err := MapToSlice(query, mp) + if err != nil { + return &Row{nil, err} + } + return db.QueryRowContext(ctx, query, args...) +} + +func (db *DB) QueryRowMap(query string, mp interface{}) *Row { + return db.QueryRowMapContext(context.Background(), query, mp) +} + +func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { + query, args, err := StructToSlice(query, st) + if err != nil { + return &Row{nil, err} + } + return db.QueryRowContext(ctx, query, args...) +} + +func (db *DB) QueryRowStruct(query string, st interface{}) *Row { + return db.QueryRowStructContext(context.Background(), query, st) +} + +var ( + re = regexp.MustCompile(`[?](\w+)`) +) + +// ExecMapContext exec map with context.Context +// insert into (name) values (?) +// insert into (name) values (?name) +func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return db.DB.ExecContext(ctx, query, args...) +} + +func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { + return db.ExecMapContext(context.Background(), query, mp) +} + +func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return db.DB.ExecContext(ctx, query, args...) +} + +func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { + return db.ExecStructContext(context.Background(), query, st) +} diff --git a/core/db_test.go b/core/db_test.go new file mode 100644 index 00000000..9abc7a64 --- /dev/null +++ b/core/db_test.go @@ -0,0 +1,684 @@ +// 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 core + +import ( + "errors" + "flag" + "os" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm/names" +) + +var ( + dbtype = flag.String("dbtype", "mysql", "database type") + dbConn = flag.String("dbConn", "root:@/core_test?charset=utf8", "database connect string") + createTableSql string +) + +func TestMain(m *testing.M) { + flag.Parse() + + switch *dbtype { + case "sqlite3": + 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, " + + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" + } + + exitCode := m.Run() + + os.Exit(exitCode) +} + +func testOpen() (*DB, error) { + switch *dbtype { + case "sqlite3": + os.Remove("./test.db") + return Open("sqlite3", "./test.db") + case "mysql": + return Open("mysql", *dbConn) + default: + panic("no db type") + } +} + +func BenchmarkOriQuery(b *testing.B) { + b.StopTimer() + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + var Id int64 + var Name, Title, Alias, NickName string + var Age float32 + var Created NullTime + err = rows.Scan(&Id, &Name, &Title, &Age, &Alias, &NickName, &Created) + if err != nil { + b.Error(err) + } + //fmt.Println(Id, Name, Title, Age, Alias, NickName) + } + rows.Close() + } +} + +type User struct { + Id int64 + Name string + Title string + Age float32 + Alias string + NickName string + Created NullTime +} + +func BenchmarkStructQuery(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByIndex(&user) + if err != nil { + b.Error(err) + } + if user.Name != "xlw" { + b.Log(user) + b.Error(errors.New("name should be xlw")) + } + } + rows.Close() + } +} + +func BenchmarkStruct2Query(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + db.Mapper = names.NewCacheMapper(&names.SnakeMapper{}) + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByName(&user) + if err != nil { + b.Error(err) + } + if user.Name != "xlw" { + b.Log(user) + b.Error(errors.New("name should be xlw")) + } + } + rows.Close() + } +} + +func BenchmarkSliceInterfaceQuery(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + cols, err := rows.Columns() + if err != nil { + b.Error(err) + } + + for rows.Next() { + slice := make([]interface{}, len(cols)) + err = rows.ScanSlice(&slice) + if err != nil { + b.Error(err) + } + b.Log(slice) + switch slice[1].(type) { + case *string: + if *slice[1].(*string) != "xlw" { + b.Error(errors.New("name should be xlw")) + } + case []byte: + if string(slice[1].([]byte)) != "xlw" { + b.Error(errors.New("name should be xlw")) + } + } + } + + rows.Close() + } +} + +/*func BenchmarkSliceBytesQuery(b *testing.B) { + b.StopTimer() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + cols, err := rows.Columns() + if err != nil { + b.Error(err) + } + + for rows.Next() { + slice := make([][]byte, len(cols)) + err = rows.ScanSlice(&slice) + if err != nil { + b.Error(err) + } + if string(slice[1]) != "xlw" { + fmt.Println(slice) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +} +*/ + +func BenchmarkSliceStringQuery(b *testing.B) { + b.StopTimer() + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name, created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + cols, err := rows.Columns() + if err != nil { + b.Error(err) + } + + for rows.Next() { + slice := make([]*string, len(cols)) + err = rows.ScanSlice(&slice) + if err != nil { + b.Error(err) + } + if (*slice[1]) != "xlw" { + b.Log(slice) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +} + +func BenchmarkMapInterfaceQuery(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + m := make(map[string]interface{}) + err = rows.ScanMap(&m) + if err != nil { + b.Error(err) + } + switch m["name"].(type) { + case string: + if m["name"].(string) != "xlw" { + b.Log(m) + b.Error(errors.New("name should be xlw")) + } + case []byte: + if string(m["name"].([]byte)) != "xlw" { + b.Log(m) + b.Error(errors.New("name should be xlw")) + } + } + } + + rows.Close() + } +} + +/*func BenchmarkMapBytesQuery(b *testing.B) { + b.StopTimer() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + m := make(map[string][]byte) + err = rows.ScanMap(&m) + if err != nil { + b.Error(err) + } + if string(m["name"]) != "xlw" { + fmt.Println(m) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +} +*/ +/* +func BenchmarkMapStringQuery(b *testing.B) { + b.StopTimer() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + m := make(map[string]string) + err = rows.ScanMap(&m) + if err != nil { + b.Error(err) + } + if m["name"] != "xlw" { + fmt.Println(m) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +}*/ + +func BenchmarkExec(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } +} + +func BenchmarkExecMap(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + mp := map[string]interface{}{ + "name": "xlw", + "title": "tester", + "age": 1.2, + "alias": "lunny", + "nick_name": "lunny xiao", + "created": time.Now(), + } + + for i := 0; i < b.N; i++ { + _, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name, created) "+ + "values (?name,?title,?age,?alias,?nick_name,?created)", + &mp) + if err != nil { + b.Error(err) + } + } +} + +func TestExecMap(t *testing.T) { + db, err := testOpen() + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + t.Error(err) + } + + mp := map[string]interface{}{ + "name": "xlw", + "title": "tester", + "age": 1.2, + "alias": "lunny", + "nick_name": "lunny xiao", + "created": time.Now(), + } + + _, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name,created) "+ + "values (?name,?title,?age,?alias,?nick_name,?created)", + &mp) + if err != nil { + t.Error(err) + } + + rows, err := db.Query("select * from user") + if err != nil { + t.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByName(&user) + if err != nil { + t.Error(err) + } + t.Log("--", user) + } +} + +func TestExecStruct(t *testing.T) { + db, err := testOpen() + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + t.Error(err) + } + + user := User{Name: "xlw", + Title: "tester", + Age: 1.2, + Alias: "lunny", + NickName: "lunny xiao", + Created: NullTime(time.Now()), + } + + _, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+ + "values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) + if err != nil { + t.Error(err) + } + + rows, err := db.QueryStruct("select * from user where `name` = ?Name", &user) + if err != nil { + t.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByName(&user) + if err != nil { + t.Error(err) + } + t.Log("1--", user) + } +} + +func BenchmarkExecStruct(b *testing.B) { + b.StopTimer() + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + user := User{Name: "xlw", + Title: "tester", + Age: 1.2, + Alias: "lunny", + NickName: "lunny xiao", + Created: NullTime(time.Now()), + } + + for i := 0; i < b.N; i++ { + _, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+ + "values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) + if err != nil { + b.Error(err) + } + } +} diff --git a/core/error.go b/core/error.go new file mode 100644 index 00000000..1fd18348 --- /dev/null +++ b/core/error.go @@ -0,0 +1,14 @@ +// 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 core + +import "errors" + +var ( + // ErrNoMapPointer represents error when no map pointer + ErrNoMapPointer = errors.New("mp should be a map's pointer") + // ErrNoStructPointer represents error when no struct pointer + ErrNoStructPointer = errors.New("mp should be a struct's pointer") +) diff --git a/core/rows.go b/core/rows.go new file mode 100644 index 00000000..a1e8bfbc --- /dev/null +++ b/core/rows.go @@ -0,0 +1,338 @@ +// 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 core + +import ( + "database/sql" + "errors" + "reflect" + "sync" +) + +type Rows struct { + *sql.Rows + db *DB +} + +func (rs *Rows) ToMapString() ([]map[string]string, error) { + cols, err := rs.Columns() + if err != nil { + return nil, err + } + + var results = make([]map[string]string, 0, 10) + for rs.Next() { + var record = make(map[string]string, len(cols)) + err = rs.ScanMap(&record) + if err != nil { + return nil, err + } + results = append(results, record) + } + return results, nil +} + +// 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") + } + + vvvs := make([]reflect.Value, len(dest)) + for i, s := range dest { + vv := reflect.ValueOf(s) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return errors.New("dest should be a struct's pointer") + } + + vvvs[i] = vv.Elem() + } + + cols, err := rs.Columns() + if err != nil { + return err + } + newDest := make([]interface{}, len(cols)) + + var i = 0 + for _, vvv := range vvvs { + for j := 0; j < vvv.NumField(); j++ { + newDest[i] = vvv.Field(j).Addr().Interface() + i = i + 1 + } + } + + return rs.Rows.Scan(newDest...) +} + +var ( + fieldCache = make(map[reflect.Type]map[string]int) + fieldCacheMutex sync.RWMutex +) + +func fieldByName(v reflect.Value, name string) reflect.Value { + t := v.Type() + fieldCacheMutex.RLock() + cache, ok := fieldCache[t] + fieldCacheMutex.RUnlock() + if !ok { + cache = make(map[string]int) + for i := 0; i < v.NumField(); i++ { + cache[t.Field(i).Name] = i + } + fieldCacheMutex.Lock() + fieldCache[t] = cache + fieldCacheMutex.Unlock() + } + + if i, ok := cache[name]; ok { + return v.Field(i) + } + + return reflect.Zero(t) +} + +// 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 { + return errors.New("dest should be a struct's pointer") + } + + cols, err := rs.Columns() + if err != nil { + return err + } + + newDest := make([]interface{}, len(cols)) + var v EmptyScanner + for j, name := range cols { + f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name)) + if f.IsValid() { + newDest[j] = f.Addr().Interface() + } else { + newDest[j] = &v + } + } + + return rs.Rows.Scan(newDest...) +} + +// 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 { + return errors.New("dest should be a slice's pointer") + } + + vvv := vv.Elem() + cols, err := rs.Columns() + if err != nil { + return err + } + + newDest := make([]interface{}, len(cols)) + + for j := 0; j < len(cols); j++ { + if j >= vvv.Len() { + newDest[j] = reflect.New(vvv.Type().Elem()).Interface() + } else { + newDest[j] = vvv.Index(j).Addr().Interface() + } + } + + err = rs.Rows.Scan(newDest...) + if err != nil { + return err + } + + srcLen := vvv.Len() + for i := srcLen; i < len(cols); i++ { + vvv = reflect.Append(vvv, reflect.ValueOf(newDest[i]).Elem()) + } + return nil +} + +// 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 { + return errors.New("dest should be a map's pointer") + } + + cols, err := rs.Columns() + if err != nil { + return err + } + + newDest := make([]interface{}, len(cols)) + vvv := vv.Elem() + + for i := range cols { + newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface() + } + + err = rs.Rows.Scan(newDest...) + if err != nil { + return err + } + + for i, name := range cols { + vname := reflect.ValueOf(name) + vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem()) + } + + return nil +} + +type Row struct { + rows *Rows + // One of these two will be non-nil: + err error // deferred error for easy chaining +} + +// ErrorRow return an error row +func ErrorRow(err error) *Row { + return &Row{ + err: err, + } +} + +// NewRow from rows +func NewRow(rows *Rows, err error) *Row { + return &Row{rows, err} +} + +func (row *Row) Columns() ([]string, error) { + if row.err != nil { + return nil, row.err + } + return row.rows.Columns() +} + +func (row *Row) Scan(dest ...interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + for _, dp := range dest { + if _, ok := dp.(*sql.RawBytes); ok { + return errors.New("sql: RawBytes isn't allowed on Row.Scan") + } + } + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.Scan(dest...) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +func (row *Row) ScanStructByName(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanStructByName(dest) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +func (row *Row) ScanStructByIndex(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanStructByIndex(dest) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +// 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 + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanSlice(dest) + if err != nil { + return err + } + + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +// scan data to a map's pointer +func (row *Row) ScanMap(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanMap(dest) + if err != nil { + return err + } + + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +func (row *Row) ToMapString() (map[string]string, error) { + cols, err := row.Columns() + if err != nil { + return nil, err + } + + var record = make(map[string]string, len(cols)) + err = row.ScanMap(&record) + if err != nil { + return nil, err + } + + return record, nil +} diff --git a/core/scan.go b/core/scan.go new file mode 100644 index 00000000..897b5341 --- /dev/null +++ b/core/scan.go @@ -0,0 +1,66 @@ +// 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 core + +import ( + "database/sql/driver" + "fmt" + "time" +) + +type NullTime time.Time + +var ( + _ driver.Valuer = NullTime{} +) + +func (ns *NullTime) Scan(value interface{}) error { + if value == nil { + return nil + } + return convertTime(ns, value) +} + +// Value implements the driver Valuer interface. +func (ns NullTime) Value() (driver.Value, error) { + if (time.Time)(ns).IsZero() { + return nil, nil + } + return (time.Time)(ns).Format("2006-01-02 15:04:05"), nil +} + +func convertTime(dest *NullTime, src interface{}) error { + // Common cases, without reflect. + switch s := src.(type) { + case string: + t, err := time.Parse("2006-01-02 15:04:05", s) + if err != nil { + return err + } + *dest = NullTime(t) + return nil + case []uint8: + t, err := time.Parse("2006-01-02 15:04:05", string(s)) + if err != nil { + return err + } + *dest = NullTime(t) + return nil + case time.Time: + *dest = NullTime(s) + return nil + case nil: + default: + return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) + } + return nil +} + +type EmptyScanner struct { +} + +func (EmptyScanner) Scan(src interface{}) error { + return nil +} diff --git a/core/stmt.go b/core/stmt.go new file mode 100644 index 00000000..8a21541a --- /dev/null +++ b/core/stmt.go @@ -0,0 +1,166 @@ +// 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 core + +import ( + "context" + "database/sql" + "errors" + "reflect" +) + +// Stmt reprents a stmt objects +type Stmt struct { + *sql.Stmt + db *DB + names map[string]int +} + +func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { + names := make(map[string]int) + var i int + query = re.ReplaceAllStringFunc(query, func(src string) string { + names[src[1:]] = i + i += 1 + return "?" + }) + + stmt, err := db.DB.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + return &Stmt{stmt, db, names}, nil +} + +func (db *DB) Prepare(query string) (*Stmt, error) { + return db.PrepareContext(context.Background(), query) +} + +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 { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + return s.Stmt.ExecContext(ctx, args...) +} + +func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { + return s.ExecMapContext(context.Background(), mp) +} + +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 { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + return s.Stmt.ExecContext(ctx, args...) +} + +func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { + return s.ExecStructContext(context.Background(), st) +} + +func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { + rows, err := s.Stmt.QueryContext(ctx, args...) + if err != nil { + return nil, err + } + return &Rows{rows, s.db}, nil +} + +func (s *Stmt) Query(args ...interface{}) (*Rows, error) { + return s.QueryContext(context.Background(), args...) +} + +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 { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + + return s.QueryContext(ctx, args...) +} + +func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { + return s.QueryMapContext(context.Background(), mp) +} + +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 { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + + return s.Query(args...) +} + +func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { + return s.QueryStructContext(context.Background(), st) +} + +func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row { + rows, err := s.QueryContext(ctx, args...) + return &Row{rows, err} +} + +func (s *Stmt) QueryRow(args ...interface{}) *Row { + return s.QueryRowContext(context.Background(), args...) +} + +func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row { + vv := reflect.ValueOf(mp) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return &Row{nil, errors.New("mp should be a map's pointer")} + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + + return s.QueryRowContext(ctx, args...) +} + +func (s *Stmt) QueryRowMap(mp interface{}) *Row { + return s.QueryRowMapContext(context.Background(), mp) +} + +func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row { + vv := reflect.ValueOf(st) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return &Row{nil, errors.New("st should be a struct's pointer")} + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + + return s.QueryRowContext(ctx, args...) +} + +func (s *Stmt) QueryRowStruct(st interface{}) *Row { + return s.QueryRowStructContext(context.Background(), st) +} diff --git a/core/tx.go b/core/tx.go new file mode 100644 index 00000000..a56b7006 --- /dev/null +++ b/core/tx.go @@ -0,0 +1,153 @@ +// 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 core + +import ( + "context" + "database/sql" +) + +type Tx struct { + *sql.Tx + db *DB +} + +func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := db.DB.BeginTx(ctx, opts) + if err != nil { + return nil, err + } + return &Tx{tx, db}, nil +} + +func (db *DB) Begin() (*Tx, error) { + tx, err := db.DB.Begin() + if err != nil { + return nil, err + } + return &Tx{tx, db}, nil +} + +func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { + names := make(map[string]int) + var i int + query = re.ReplaceAllStringFunc(query, func(src string) string { + names[src[1:]] = i + i += 1 + return "?" + }) + + stmt, err := tx.Tx.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + return &Stmt{stmt, tx.db, names}, nil +} + +func (tx *Tx) Prepare(query string) (*Stmt, error) { + return tx.PrepareContext(context.Background(), query) +} + +func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt { + stmt.Stmt = tx.Tx.StmtContext(ctx, stmt.Stmt) + return stmt +} + +func (tx *Tx) Stmt(stmt *Stmt) *Stmt { + return tx.StmtContext(context.Background(), stmt) +} + +func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return tx.Tx.ExecContext(ctx, query, args...) +} + +func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { + return tx.ExecMapContext(context.Background(), query, mp) +} + +func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return tx.Tx.ExecContext(ctx, query, args...) +} + +func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { + return tx.ExecStructContext(context.Background(), query, st) +} + +func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + rows, err := tx.Tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + return &Rows{rows, tx.db}, nil +} + +func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { + return tx.QueryContext(context.Background(), query, args...) +} + +func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return tx.QueryContext(ctx, query, args...) +} + +func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { + return tx.QueryMapContext(context.Background(), query, mp) +} + +func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return tx.QueryContext(ctx, query, args...) +} + +func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { + return tx.QueryStructContext(context.Background(), query, st) +} + +func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := tx.QueryContext(ctx, query, args...) + return &Row{rows, err} +} + +func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { + return tx.QueryRowContext(context.Background(), query, args...) +} + +func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { + query, args, err := MapToSlice(query, mp) + if err != nil { + return &Row{nil, err} + } + return tx.QueryRowContext(ctx, query, args...) +} + +func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { + return tx.QueryRowMapContext(context.Background(), query, mp) +} + +func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { + query, args, err := StructToSlice(query, st) + if err != nil { + return &Row{nil, err} + } + return tx.QueryRowContext(ctx, query, args...) +} + +func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { + return tx.QueryRowStructContext(context.Background(), query, st) +} diff --git a/dialects/dialect.go b/dialects/dialect.go new file mode 100644 index 00000000..b88d4e61 --- /dev/null +++ b/dialects/dialect.go @@ -0,0 +1,410 @@ +// 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 dialects + +import ( + "fmt" + "strings" + "time" + + "xorm.io/xorm/core" + "xorm.io/xorm/log" + "xorm.io/xorm/schemas" +) + +type DBType string + +type URI struct { + DBType DBType + Proto string + Host string + Port string + DBName string + User string + Passwd string + Charset string + Laddr string + Raddr string + Timeout time.Duration + Schema string +} + +// a dialect is a driver's wrapper +type Dialect interface { + SetLogger(logger log.Logger) + Init(*core.DB, *URI, string, string) error + URI() *URI + DB() *core.DB + DBType() DBType + SQLType(*schemas.Column) string + FormatBytes(b []byte) string + + DriverName() string + DataSourceName() string + + IsReserved(string) bool + Quote(string) string + + AndStr() string + OrStr() string + EqStr() string + RollBackStr() string + AutoIncrStr() string + + SupportInsertMany() bool + SupportEngine() bool + SupportCharset() bool + SupportDropIfExists() bool + IndexOnTable() bool + ShowCreateNull() bool + + IndexCheckSQL(tableName, idxName string) (string, []interface{}) + TableCheckSQL(tableName string) (string, []interface{}) + + IsColumnExist(tableName string, colName string) (bool, error) + + CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string + DropTableSQL(tableName string) string + CreateIndexSQL(tableName string, index *schemas.Index) string + DropIndexSQL(tableName string, index *schemas.Index) string + + ModifyColumnSQL(tableName string, col *schemas.Column) string + + ForUpdateSQL(query string) string + + // CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error + // MustDropTable(tableName string) error + + GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) + GetTables() ([]*schemas.Table, error) + GetIndexes(tableName string) (map[string]*schemas.Index, error) + + Filters() []Filter + SetParams(params map[string]string) +} + +func OpenDialect(dialect Dialect) (*core.DB, error) { + return core.Open(dialect.DriverName(), dialect.DataSourceName()) +} + +// Base represents a basic dialect and all real dialects could embed this struct +type Base struct { + db *core.DB + dialect Dialect + driverName string + dataSourceName string + logger log.Logger + uri *URI +} + +// String generate column description string according dialect +func String(d Dialect, col *schemas.Column) string { + sql := d.Quote(col.Name) + " " + + sql += d.SQLType(col) + " " + + if col.IsPrimaryKey { + sql += "PRIMARY KEY " + if col.IsAutoIncrement { + sql += d.AutoIncrStr() + " " + } + } + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + if d.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + return sql +} + +// StringNoPk generate column description string according dialect without primary keys +func StringNoPk(d Dialect, col *schemas.Column) string { + sql := d.Quote(col.Name) + " " + + sql += d.SQLType(col) + " " + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + if d.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + return sql +} + +func (b *Base) DB() *core.DB { + return b.db +} + +func (b *Base) SetLogger(logger log.Logger) { + b.logger = logger +} + +func (b *Base) Init(db *core.DB, dialect Dialect, uri *URI, drivername, dataSourceName string) error { + b.db, b.dialect, b.uri = db, dialect, uri + b.driverName, b.dataSourceName = drivername, dataSourceName + return nil +} + +func (b *Base) URI() *URI { + return b.uri +} + +func (b *Base) DBType() DBType { + return b.uri.DBType +} + +func (b *Base) FormatBytes(bs []byte) string { + return fmt.Sprintf("0x%x", bs) +} + +func (b *Base) DriverName() string { + return b.driverName +} + +func (b *Base) ShowCreateNull() bool { + return true +} + +func (b *Base) DataSourceName() string { + return b.dataSourceName +} + +func (b *Base) AndStr() string { + return "AND" +} + +func (b *Base) OrStr() string { + return "OR" +} + +func (b *Base) EqStr() string { + return "=" +} + +func (db *Base) RollBackStr() string { + return "ROLL BACK" +} + +func (db *Base) SupportDropIfExists() bool { + return true +} + +func (db *Base) DropTableSQL(tableName string) string { + quote := db.dialect.Quote + return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)) +} + +func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { + db.LogSQL(query, args) + rows, err := db.DB().Query(query, args...) + if err != nil { + return false, err + } + defer rows.Close() + + if rows.Next() { + return true, nil + } + return false, nil +} + +func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { + query := fmt.Sprintf( + "SELECT %v FROM %v.%v WHERE %v = ? AND %v = ? AND %v = ?", + db.dialect.Quote("COLUMN_NAME"), + db.dialect.Quote("INFORMATION_SCHEMA"), + db.dialect.Quote("COLUMNS"), + db.dialect.Quote("TABLE_SCHEMA"), + db.dialect.Quote("TABLE_NAME"), + db.dialect.Quote("COLUMN_NAME"), + ) + return db.HasRecords(query, db.uri.DBName, tableName, colName) +} + +/* +func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error { + sql, args := db.dialect.TableCheckSQL(tableName) + rows, err := db.DB().Query(sql, args...) + if db.Logger != nil { + db.Logger.Info("[sql]", sql, args) + } + if err != nil { + return err + } + defer rows.Close() + + if rows.Next() { + return nil + } + + sql = db.dialect.CreateTableSQL(table, tableName, storeEngine, charset) + _, err = db.DB().Exec(sql) + if db.Logger != nil { + db.Logger.Info("[sql]", sql) + } + return err +}*/ + +func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quote + var unique string + var idxName string + if index.Type == schemas.UniqueType { + unique = " UNIQUE" + } + idxName = index.XName(tableName) + return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, + quote(idxName), quote(tableName), + quote(strings.Join(index.Cols, quote(",")))) +} + +func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quote + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name + } + return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName)) +} + +func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { + return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, StringNoPk(db.dialect, col)) +} + +func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { + var sql string + sql = "CREATE TABLE IF NOT EXISTS " + if tableName == "" { + tableName = table.Name + } + + sql += b.dialect.Quote(tableName) + sql += " (" + + if len(table.ColumnsSeq()) > 0 { + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += String(b.dialect, col) + } else { + sql += StringNoPk(b.dialect, col) + } + sql = strings.TrimSpace(sql) + if b.DriverName() == schemas.MYSQL && len(col.Comment) > 0 { + sql += " COMMENT '" + col.Comment + "'" + } + sql += ", " + } + + if len(pkList) > 1 { + sql += "PRIMARY KEY ( " + sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) + sql += " ), " + } + + sql = sql[:len(sql)-2] + } + sql += ")" + + if b.dialect.SupportEngine() && storeEngine != "" { + sql += " ENGINE=" + storeEngine + } + if b.dialect.SupportCharset() { + if len(charset) == 0 { + charset = b.dialect.URI().Charset + } + if len(charset) > 0 { + sql += " DEFAULT CHARSET " + charset + } + } + + return sql +} + +func (b *Base) ForUpdateSQL(query string) string { + return query + " FOR UPDATE" +} + +func (b *Base) LogSQL(sql string, args []interface{}) { + if b.logger != nil && b.logger.IsShowSQL() { + if len(args) > 0 { + b.logger.Infof("[SQL] %v %v", sql, args) + } else { + b.logger.Infof("[SQL] %v", sql) + } + } +} + +func (b *Base) SetParams(params map[string]string) { +} + +var ( + dialects = map[string]func() Dialect{} +) + +// RegisterDialect register database dialect +func RegisterDialect(dbName DBType, dialectFunc func() Dialect) { + if dialectFunc == nil { + panic("core: Register dialect is nil") + } + dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect +} + +// QueryDialect query if registered database dialect +func QueryDialect(dbName DBType) Dialect { + if d, ok := dialects[strings.ToLower(string(dbName))]; ok { + return d() + } + return nil +} + +func regDrvsNDialects() bool { + providedDrvsNDialects := map[string]struct { + dbType DBType + getDriver func() Driver + getDialect func() Dialect + }{ + "mssql": {"mssql", func() Driver { return &odbcDriver{} }, func() Dialect { return &mssql{} }}, + "odbc": {"mssql", func() Driver { return &odbcDriver{} }, func() Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access + "mysql": {"mysql", func() Driver { return &mysqlDriver{} }, func() Dialect { return &mysql{} }}, + "mymysql": {"mysql", func() Driver { return &mymysqlDriver{} }, func() Dialect { return &mysql{} }}, + "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{} }}, + "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, + "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, + } + + for driverName, v := range providedDrvsNDialects { + if driver := QueryDriver(driverName); driver == nil { + RegisterDriver(driverName, v.getDriver()) + RegisterDialect(v.dbType, v.getDialect) + } + } + return true +} + +func init() { + regDrvsNDialects() +} diff --git a/dialects/driver.go b/dialects/driver.go new file mode 100644 index 00000000..5343d594 --- /dev/null +++ b/dialects/driver.go @@ -0,0 +1,31 @@ +// 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 dialects + +type Driver interface { + Parse(string, string) (*URI, error) +} + +var ( + drivers = map[string]Driver{} +) + +func RegisterDriver(driverName string, driver Driver) { + if driver == nil { + panic("core: Register driver is nil") + } + if _, dup := drivers[driverName]; dup { + panic("core: Register called twice for driver " + driverName) + } + drivers[driverName] = driver +} + +func QueryDriver(driverName string) Driver { + return drivers[driverName] +} + +func RegisteredDriverSize() int { + return len(drivers) +} diff --git a/dialects/filter.go b/dialects/filter.go new file mode 100644 index 00000000..f7bad1a9 --- /dev/null +++ b/dialects/filter.go @@ -0,0 +1,95 @@ +// 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 dialects + +import ( + "fmt" + "strings" + + "xorm.io/xorm/schemas" +) + +// Filter is an interface to filter SQL +type Filter interface { + Do(sql string, dialect Dialect, table *schemas.Table) string +} + +// QuoteFilter filter SQL replace ` to database's own quote character +type QuoteFilter struct { +} + +func (s *QuoteFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { + dummy := dialect.Quote("") + if len(dummy) != 2 { + return sql + } + prefix, suffix := dummy[0], dummy[1] + raw := []byte(sql) + for i, cnt := 0, 0; i < len(raw); i = i + 1 { + if raw[i] == '`' { + if cnt%2 == 0 { + raw[i] = prefix + } else { + raw[i] = suffix + } + cnt++ + } + } + return string(raw) +} + +// IdFilter filter SQL replace (id) to primary key column name +type IdFilter struct { +} + +type Quoter struct { + dialect Dialect +} + +func NewQuoter(dialect Dialect) *Quoter { + return &Quoter{dialect} +} + +func (q *Quoter) Quote(content string) string { + return q.dialect.Quote(content) +} + +func (i *IdFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { + quoter := NewQuoter(dialect) + if table != nil && len(table.PrimaryKeys) == 1 { + sql = strings.Replace(sql, " `(id)` ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) + sql = strings.Replace(sql, " "+quoter.Quote("(id)")+" ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) + return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) + } + return sql +} + +// SeqFilter filter SQL replace ?, ? ... to $1, $2 ... +type SeqFilter struct { + Prefix string + Start int +} + +func convertQuestionMark(sql, prefix string, start int) string { + var buf strings.Builder + var beginSingleQuote bool + var index = start + for _, c := range sql { + if !beginSingleQuote && c == '?' { + buf.WriteString(fmt.Sprintf("%s%v", prefix, index)) + index++ + } else { + if c == '\'' { + beginSingleQuote = !beginSingleQuote + } + buf.WriteRune(c) + } + } + return buf.String() +} + +func (s *SeqFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { + return convertQuestionMark(sql, s.Prefix, s.Start) +} diff --git a/dialects/filter_test.go b/dialects/filter_test.go new file mode 100644 index 00000000..e5430bab --- /dev/null +++ b/dialects/filter_test.go @@ -0,0 +1,39 @@ +package dialects + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type quoterOnly struct { + Dialect +} + +func (q *quoterOnly) Quote(item string) string { + return "[" + item + "]" +} + +func TestQuoteFilter_Do(t *testing.T) { + f := QuoteFilter{} + sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" + res := f.Do(sql, new(quoterOnly), nil) + assert.EqualValues(t, + "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", + res, + ) +} + +func TestSeqFilter(t *testing.T) { + var kases = map[string]string{ + "SELECT * FROM TABLE1 WHERE a=? AND b=?": "SELECT * FROM TABLE1 WHERE a=$1 AND b=$2", + "SELECT 1, '???', '2006-01-02 15:04:05' FROM TABLE1 WHERE a=? AND b=?": "SELECT 1, '???', '2006-01-02 15:04:05' FROM TABLE1 WHERE a=$1 AND b=$2", + "select '1''?' from issue": "select '1''?' from issue", + "select '1\\??' from issue": "select '1\\??' from issue", + "select '1\\\\',? from issue": "select '1\\\\',$1 from issue", + "select '1\\''?',? from issue": "select '1\\''?',$1 from issue", + } + for sql, result := range kases { + assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1)) + } +} diff --git a/dialect_mssql.go b/dialects/mssql.go similarity index 86% rename from dialect_mssql.go rename to dialects/mssql.go index 29070da2..99b1c782 100644 --- a/dialect_mssql.go +++ b/dialects/mssql.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "errors" @@ -11,7 +11,8 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -205,64 +206,64 @@ var ( ) type mssql struct { - core.Base + Base } -func (db *mssql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *mssql) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (db *mssql) SqlType(c *core.Column) string { +func (db *mssql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.Bool: - res = core.Bit + case schemas.Bool: + res = schemas.Bit if strings.EqualFold(c.Default, "true") { c.Default = "1" } else if strings.EqualFold(c.Default, "false") { c.Default = "0" } - case core.Serial: + case schemas.Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.Int - case core.BigSerial: + res = schemas.Int + case schemas.BigSerial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.BigInt - case core.Bytea, core.Blob, core.Binary, core.TinyBlob, core.MediumBlob, core.LongBlob: - res = core.VarBinary + res = schemas.BigInt + case schemas.Bytea, schemas.Blob, schemas.Binary, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: + res = schemas.VarBinary if c.Length == 0 { c.Length = 50 } - case core.TimeStamp: - res = core.DateTime - case core.TimeStampz: + case schemas.TimeStamp: + res = schemas.DateTime + case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case core.MediumInt: - res = core.Int - case core.Text, core.MediumText, core.TinyText, core.LongText, core.Json: - res = core.Varchar + "(MAX)" - case core.Double: - res = core.Real - case core.Uuid: - res = core.Varchar + case schemas.MediumInt: + res = schemas.Int + case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: + res = schemas.Varchar + "(MAX)" + case schemas.Double: + res = schemas.Real + case schemas.Uuid: + res = schemas.Varchar c.Length = 40 - case core.TinyInt: - res = core.TinyInt + case schemas.TinyInt: + res = schemas.TinyInt c.Length = 0 - case core.BigInt: - res = core.BigInt + case schemas.BigInt: + res = schemas.BigInt c.Length = 0 default: res = t } - if res == core.Int { - return core.Int + if res == schemas.Int { + return schemas.Int } hasLen1 := (c.Length > 0) @@ -297,7 +298,7 @@ func (db *mssql) AutoIncrStr() string { return "IDENTITY" } -func (db *mssql) DropTableSql(tableName string) string { +func (db *mssql) DropTableSQL(tableName string) string { return fmt.Sprintf("IF EXISTS (SELECT * FROM sysobjects WHERE id = "+ "object_id(N'%s') and OBJECTPROPERTY(id, N'IsUserTable') = 1) "+ "DROP TABLE \"%s\"", tableName, tableName) @@ -311,7 +312,7 @@ func (db *mssql) IndexOnTable() bool { return true } -func (db *mssql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { +func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" return sql, args @@ -329,13 +330,13 @@ func (db *mssql) IsColumnExist(tableName, colName string) (bool, error) { return db.HasRecords(query, tableName, colName) } -func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { +func (db *mssql) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{} sql := "select * from sysobjects where id = object_id(N'" + tableName + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1" return sql, args } -func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *mssql) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{} s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, "default_is_null" = (CASE WHEN c.text is null THEN 1 ELSE 0 END), @@ -357,7 +358,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { var name, ctype, vdefault string @@ -368,7 +369,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column return nil, nil, err } - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) col.Name = strings.Trim(name, "` ") col.Nullable = nullable @@ -387,14 +388,14 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column } switch ct { case "DATETIMEOFFSET": - col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NVARCHAR": - col.SQLType = core.SQLType{Name: core.NVarchar, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: 0, DefaultLength2: 0} case "IMAGE": - col.SQLType = core.SQLType{Name: core.VarBinary, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0} default: - if _, ok := core.SqlTypes[ct]; ok { - col.SQLType = core.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} + if _, ok := schemas.SqlTypes[ct]; ok { + col.SQLType = schemas.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} } else { return nil, nil, fmt.Errorf("Unknown colType %v for %v - %v", ct, tableName, col.Name) } @@ -406,7 +407,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column return colSeq, cols, nil } -func (db *mssql) GetTables() ([]*core.Table, error) { +func (db *mssql) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := `select name from sysobjects where xtype ='U'` db.LogSQL(s, args) @@ -417,9 +418,9 @@ func (db *mssql) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) if err != nil { @@ -431,7 +432,7 @@ func (db *mssql) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *mssql) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *mssql) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := `SELECT IXS.NAME AS [INDEX_NAME], @@ -452,7 +453,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, colName, isUnique string @@ -468,9 +469,9 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } if i { - indexType = core.UniqueType + indexType = schemas.UniqueType } else { - indexType = core.IndexType + indexType = schemas.IndexType } colName = strings.Trim(colName, "` ") @@ -480,10 +481,10 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? isRegular = true } - var index *core.Index + var index *schemas.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(core.Index) + index = new(schemas.Index) index.Type = indexType index.Name = indexName index.IsRegular = isRegular @@ -494,7 +495,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? return indexes, nil } -func (db *mssql) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *mssql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string if tableName == "" { tableName = table.Name @@ -509,9 +510,9 @@ func (db *mssql) CreateTableSql(table *core.Table, tableName, storeEngine, chars for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += col.String(db) + sql += String(db, col) } else { - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) } sql = strings.TrimSpace(sql) sql += ", " @@ -528,18 +529,18 @@ func (db *mssql) CreateTableSql(table *core.Table, tableName, storeEngine, chars return sql } -func (db *mssql) ForUpdateSql(query string) string { +func (db *mssql) ForUpdateSQL(query string) string { return query } -func (db *mssql) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}} +func (db *mssql) Filters() []Filter { + return []Filter{&IdFilter{}, &QuoteFilter{}} } type odbcDriver struct { } -func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) { var dbName string if strings.HasPrefix(dataSourceName, "sqlserver://") { @@ -563,5 +564,5 @@ func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) if dbName == "" { return nil, errors.New("no db name provided") } - return &core.Uri{DbName: dbName, DbType: core.MSSQL}, nil + return &URI{DBName: dbName, DBType: schemas.MSSQL}, nil } diff --git a/dialect_mssql_test.go b/dialects/mssql_test.go similarity index 84% rename from dialect_mssql_test.go rename to dialects/mssql_test.go index acd1d059..168f1777 100644 --- a/dialect_mssql_test.go +++ b/dialects/mssql_test.go @@ -2,13 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "reflect" "testing" - - "xorm.io/core" ) func TestParseMSSQL(t *testing.T) { @@ -21,15 +19,15 @@ func TestParseMSSQL(t *testing.T) { {"server=localhost;user id=sa;password=yourStrong(!)Password;database=db", "db", true}, } - driver := core.QueryDriver("mssql") + driver := QueryDriver("mssql") for _, test := range tests { uri, err := driver.Parse("mssql", 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) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } } } diff --git a/dialect_mysql.go b/dialects/mysql.go similarity index 86% rename from dialect_mysql.go rename to dialects/mysql.go index cf1dbb6f..39ed4b83 100644 --- a/dialect_mysql.go +++ b/dialects/mysql.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "crypto/tls" @@ -13,7 +13,8 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -162,7 +163,7 @@ var ( ) type mysql struct { - core.Base + Base net string addr string params map[string]string @@ -175,7 +176,7 @@ type mysql struct { rowFormat string } -func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *mysql) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } @@ -199,29 +200,29 @@ func (db *mysql) SetParams(params map[string]string) { } } -func (db *mysql) SqlType(c *core.Column) string { +func (db *mysql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.Bool: - res = core.TinyInt + case schemas.Bool: + res = schemas.TinyInt c.Length = 1 - case core.Serial: + case schemas.Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.Int - case core.BigSerial: + res = schemas.Int + case schemas.BigSerial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.BigInt - case core.Bytea: - res = core.Blob - case core.TimeStampz: - res = core.Char + res = schemas.BigInt + case schemas.Bytea: + res = schemas.Blob + case schemas.TimeStampz: + res = schemas.Char c.Length = 64 - case core.Enum: // mysql enum - res = core.Enum + case schemas.Enum: // mysql enum + res = schemas.Enum res += "(" opts := "" for v := range c.EnumOptions { @@ -229,8 +230,8 @@ func (db *mysql) SqlType(c *core.Column) string { } res += strings.TrimLeft(opts, ",") res += ")" - case core.Set: // mysql set - res = core.Set + case schemas.Set: // mysql set + res = schemas.Set res += "(" opts := "" for v := range c.SetOptions { @@ -238,13 +239,13 @@ func (db *mysql) SqlType(c *core.Column) string { } res += strings.TrimLeft(opts, ",") res += ")" - case core.NVarchar: - res = core.Varchar - case core.Uuid: - res = core.Varchar + case schemas.NVarchar: + res = schemas.Varchar + case schemas.Uuid: + res = schemas.Varchar c.Length = 40 - case core.Json: - res = core.Text + case schemas.Json: + res = schemas.Text default: res = t } @@ -252,7 +253,7 @@ func (db *mysql) SqlType(c *core.Column) string { hasLen1 := (c.Length > 0) hasLen2 := (c.Length2 > 0) - if res == core.BigInt && !hasLen1 && !hasLen2 { + if res == schemas.BigInt && !hasLen1 && !hasLen2 { c.Length = 20 hasLen1 = true } @@ -294,8 +295,8 @@ func (db *mysql) IndexOnTable() bool { return true } -func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{db.DbName, tableName, idxName} +func (db *mysql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { + args := []interface{}{db.uri.DBName, tableName, idxName} sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?" return sql, args @@ -307,14 +308,14 @@ func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{} return sql, args }*/ -func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{db.DbName, tableName} +func (db *mysql) TableCheckSQL(tableName string) (string, []interface{}) { + args := []interface{}{db.uri.DBName, tableName} sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" return sql, args } -func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { - args := []interface{}{db.DbName, tableName} +func (db *mysql) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { + args := []interface{}{db.uri.DBName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" db.LogSQL(s, args) @@ -325,10 +326,10 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) var columnName, isNullable, colType, colKey, extra, comment string @@ -356,7 +357,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column var len1, len2 int if len(cts) == 2 { idx := strings.Index(cts[1], ")") - if colType == core.Enum && cts[1][0] == '\'' { // enum + if colType == schemas.Enum && cts[1][0] == '\'' { // enum options := strings.Split(cts[1][0:idx], ",") col.EnumOptions = make(map[string]int) for k, v := range options { @@ -364,7 +365,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column v = strings.Trim(v, "'") col.EnumOptions[v] = k } - } else if colType == core.Set && cts[1][0] == '\'' { + } else if colType == schemas.Set && cts[1][0] == '\'' { options := strings.Split(cts[1][0:idx], ",") col.SetOptions = make(map[string]int) for k, v := range options { @@ -394,8 +395,8 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column } col.Length = len1 col.Length2 = len2 - if _, ok := core.SqlTypes[colType]; ok { - col.SQLType = core.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2} + 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) } @@ -424,8 +425,8 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column return colSeq, cols, nil } -func (db *mysql) GetTables() ([]*core.Table, error) { - args := []interface{}{db.DbName} +func (db *mysql) GetTables() ([]*schemas.Table, error) { + args := []interface{}{db.uri.DBName} s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" db.LogSQL(s, args) @@ -436,9 +437,9 @@ func (db *mysql) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() var name, engine, tableRows, comment string var autoIncr *string err = rows.Scan(&name, &engine, &tableRows, &autoIncr, &comment) @@ -454,8 +455,8 @@ func (db *mysql) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { - args := []interface{}{db.DbName, tableName} +func (db *mysql) GetIndexes(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` = ?" db.LogSQL(s, args) @@ -465,7 +466,7 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, colName, nonUnique string @@ -479,9 +480,9 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { } if "YES" == nonUnique || nonUnique == "1" { - indexType = core.IndexType + indexType = schemas.IndexType } else { - indexType = core.UniqueType + indexType = schemas.UniqueType } colName = strings.Trim(colName, "` ") @@ -491,10 +492,10 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { isRegular = true } - var index *core.Index + var index *schemas.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(core.Index) + index = new(schemas.Index) index.IsRegular = isRegular index.Type = indexType index.Name = indexName @@ -505,7 +506,7 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { return indexes, nil } -func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { @@ -521,9 +522,9 @@ func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, chars for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += col.String(db) + sql += String(db, col) } else { - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) } sql = strings.TrimSpace(sql) if len(col.Comment) > 0 { @@ -559,15 +560,15 @@ func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, chars return sql } -func (db *mysql) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}} +func (db *mysql) Filters() []Filter { + return []Filter{&IdFilter{}} } type mymysqlDriver struct { } -func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.MYSQL} +func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { + uri := &URI{DBType: schemas.MYSQL} pd := strings.SplitN(dataSourceName, "*", 2) if len(pd) == 2 { @@ -576,9 +577,9 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, err if len(p) != 2 { return nil, errors.New("Wrong protocol part of URI") } - db.Proto = p[0] + uri.Proto = p[0] options := strings.Split(p[1], ",") - db.Raddr = options[0] + uri.Raddr = options[0] for _, o := range options[1:] { kv := strings.SplitN(o, "=", 2) var k, v string @@ -589,13 +590,13 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, err } switch k { case "laddr": - db.Laddr = v + uri.Laddr = v case "timeout": to, err := time.ParseDuration(v) if err != nil { return nil, err } - db.Timeout = to + uri.Timeout = to default: return nil, errors.New("Unknown option: " + k) } @@ -608,17 +609,17 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, err if len(dup) != 3 { return nil, errors.New("Wrong database part of URI") } - db.DbName = dup[0] - db.User = dup[1] - db.Passwd = dup[2] + uri.DBName = dup[0] + uri.User = dup[1] + uri.Passwd = dup[2] - return db, nil + return uri, nil } type mysqlDriver struct { } -func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] @@ -628,12 +629,12 @@ func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error // tlsConfigRegister := make(map[string]*tls.Config) names := dsnPattern.SubexpNames() - uri := &core.Uri{DbType: core.MYSQL} + uri := &URI{DBType: schemas.MYSQL} for i, match := range matches { switch names[i] { case "dbname": - uri.DbName = match + uri.DBName = match case "params": if len(match) > 0 { kvs := strings.Split(match, "&") diff --git a/dialect_oracle.go b/dialects/oracle.go similarity index 90% rename from dialect_oracle.go rename to dialects/oracle.go index 15010ca5..501a22c7 100644 --- a/dialect_oracle.go +++ b/dialects/oracle.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "errors" @@ -11,7 +11,8 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -499,29 +500,29 @@ var ( ) type oracle struct { - core.Base + Base } -func (db *oracle) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *oracle) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (db *oracle) SqlType(c *core.Column) string { +func (db *oracle) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool, core.Serial, core.BigSerial: + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Bool, schemas.Serial, schemas.BigSerial: res = "NUMBER" - case core.Binary, core.VarBinary, core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob, core.Bytea: - return core.Blob - case core.Time, core.DateTime, core.TimeStamp: - res = core.TimeStamp - case core.TimeStampz: + case schemas.Binary, schemas.VarBinary, schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea: + return schemas.Blob + case schemas.Time, schemas.DateTime, schemas.TimeStamp: + res = schemas.TimeStamp + case schemas.TimeStampz: res = "TIMESTAMP WITH TIME ZONE" - case core.Float, core.Double, core.Numeric, core.Decimal: + case schemas.Float, schemas.Double, schemas.Numeric, schemas.Decimal: res = "NUMBER" - case core.Text, core.MediumText, core.LongText, core.Json: + case schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: res = "CLOB" - case core.Char, core.Varchar, core.TinyText: + case schemas.Char, schemas.Varchar, schemas.TinyText: res = "VARCHAR2" default: res = t @@ -571,11 +572,11 @@ func (db *oracle) IndexOnTable() bool { return false } -func (db *oracle) DropTableSql(tableName string) string { +func (db *oracle) DropTableSQL(tableName string) string { return fmt.Sprintf("DROP TABLE `%s`", tableName) } -func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE " if tableName == "" { @@ -591,7 +592,7 @@ func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, char /*if col.IsPrimaryKey && len(pkList) == 1 { sql += col.String(b.dialect) } else {*/ - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) // } sql = strings.TrimSpace(sql) sql += ", " @@ -618,19 +619,19 @@ func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, char return sql } -func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) { +func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{tableName, idxName} return `SELECT INDEX_NAME FROM USER_INDEXES ` + `WHERE TABLE_NAME = :1 AND INDEX_NAME = :2`, args } -func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { +func (db *oracle) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{tableName} return `SELECT table_name FROM user_tables WHERE table_name = :1`, args } func (db *oracle) MustDropTable(tableName string) error { - sql, args := db.TableCheckSql(tableName) + sql, args := db.TableCheckSQL(tableName) db.LogSQL(sql, args) rows, err := db.DB().Query(sql, args...) @@ -674,7 +675,7 @@ func (db *oracle) IsColumnExist(tableName, colName string) (bool, error) { return false, nil } -func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *oracle) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" @@ -686,10 +687,10 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) var colName, colDefault, nullable, dataType, dataPrecision, dataScale *string @@ -731,30 +732,30 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum switch dt { case "VARCHAR2": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: len1, DefaultLength2: len2} case "NVARCHAR2": - col.SQLType = core.SQLType{Name: core.NVarchar, DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: len1, DefaultLength2: len2} case "TIMESTAMP WITH TIME ZONE": - col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NUMBER": - col.SQLType = core.SQLType{Name: core.Double, DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: schemas.Double, DefaultLength: len1, DefaultLength2: len2} case "LONG", "LONG RAW": - col.SQLType = core.SQLType{Name: core.Text, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Text, DefaultLength: 0, DefaultLength2: 0} case "RAW": - col.SQLType = core.SQLType{Name: core.Binary, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: 0, DefaultLength2: 0} case "ROWID": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 18, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 18, DefaultLength2: 0} case "AQ$_SUBSCRIBERS": ignore = true default: - col.SQLType = core.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} } if ignore { continue } - if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { return nil, nil, fmt.Errorf("Unknown colType %v %v", *dataType, col.SQLType) } @@ -772,7 +773,7 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum return colSeq, cols, nil } -func (db *oracle) GetTables() ([]*core.Table, error) { +func (db *oracle) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT table_name FROM user_tables" db.LogSQL(s, args) @@ -783,9 +784,9 @@ func (db *oracle) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() err = rows.Scan(&table.Name) if err != nil { return nil, err @@ -796,7 +797,7 @@ func (db *oracle) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *oracle) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{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 =:1" @@ -808,7 +809,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, colName, uniqueness string @@ -827,15 +828,15 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { } if uniqueness == "UNIQUE" { - indexType = core.UniqueType + indexType = schemas.UniqueType } else { - indexType = core.IndexType + indexType = schemas.IndexType } - var index *core.Index + var index *schemas.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(core.Index) + index = new(schemas.Index) index.Type = indexType index.Name = indexName index.IsRegular = isRegular @@ -846,15 +847,15 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { return indexes, nil } -func (db *oracle) Filters() []core.Filter { - return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{Prefix: ":", Start: 1}, &core.IdFilter{}} +func (db *oracle) Filters() []Filter { + return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: ":", Start: 1}, &IdFilter{}} } type goracleDriver struct { } -func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.ORACLE} +func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*URI, error) { + db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] @@ -867,10 +868,10 @@ func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*core.Uri, e for i, match := range matches { switch names[i] { case "dbname": - db.DbName = match + db.DBName = match } } - if db.DbName == "" { + if db.DBName == "" { return nil, errors.New("dbname is empty") } return db, nil @@ -881,8 +882,8 @@ type oci8Driver struct { // dataSourceName=user/password@ipv4:port/dbname // dataSourceName=user/password@[ipv6]:port/dbname -func (p *oci8Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.ORACLE} +func (p *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { + db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?P.*)\/(?P.*)@` + // user:password@ `(?P.*)` + // ip:port @@ -892,10 +893,10 @@ func (p *oci8Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) for i, match := range matches { switch names[i] { case "dbname": - db.DbName = match + db.DBName = match } } - if db.DbName == "" { + if db.DBName == "" { return nil, errors.New("dbname is empty") } return db, nil diff --git a/dialect_postgres.go b/dialects/postgres.go similarity index 91% rename from dialect_postgres.go rename to dialects/postgres.go index ac6d4fe8..e4f4b89b 100644 --- a/dialect_postgres.go +++ b/dialects/postgres.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "errors" @@ -11,7 +11,8 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) // from http://www.postgresql.org/docs/current/static/sql-keywords-appendix.html @@ -769,67 +770,67 @@ var ( DefaultPostgresSchema = "public" ) -const postgresPublicSchema = "public" +const PostgresPublicSchema = "public" type postgres struct { - core.Base + Base } -func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *postgres) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { err := db.Base.Init(d, db, uri, drivername, dataSourceName) if err != nil { return err } - if db.Schema == "" { - db.Schema = DefaultPostgresSchema + if db.uri.Schema == "" { + db.uri.Schema = DefaultPostgresSchema } return nil } -func (db *postgres) SqlType(c *core.Column) string { +func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.TinyInt: - res = core.SmallInt + case schemas.TinyInt: + res = schemas.SmallInt return res - case core.Bit: - res = core.Boolean + case schemas.Bit: + res = schemas.Boolean return res - case core.MediumInt, core.Int, core.Integer: + case schemas.MediumInt, schemas.Int, schemas.Integer: if c.IsAutoIncrement { - return core.Serial + return schemas.Serial } - return core.Integer - case core.BigInt: + return schemas.Integer + case schemas.BigInt: if c.IsAutoIncrement { - return core.BigSerial + return schemas.BigSerial } - return core.BigInt - case core.Serial, core.BigSerial: + return schemas.BigInt + case schemas.Serial, schemas.BigSerial: c.IsAutoIncrement = true c.Nullable = false res = t - case core.Binary, core.VarBinary: - return core.Bytea - case core.DateTime: - res = core.TimeStamp - case core.TimeStampz: + case schemas.Binary, schemas.VarBinary: + return schemas.Bytea + case schemas.DateTime: + res = schemas.TimeStamp + case schemas.TimeStampz: return "timestamp with time zone" - case core.Float: - res = core.Real - case core.TinyText, core.MediumText, core.LongText: - res = core.Text - case core.NVarchar: - res = core.Varchar - case core.Uuid: - return core.Uuid - case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: - return core.Bytea - case core.Double: + case schemas.Float: + res = schemas.Real + case schemas.TinyText, schemas.MediumText, schemas.LongText: + res = schemas.Text + case schemas.NVarchar: + res = schemas.Varchar + case schemas.Uuid: + return schemas.Uuid + case schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: + return schemas.Bytea + case schemas.Double: return "DOUBLE PRECISION" default: if c.IsAutoIncrement { - return core.Serial + return schemas.Serial } res = t } @@ -879,37 +880,37 @@ func (db *postgres) IndexOnTable() bool { return false } -func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - if len(db.Schema) == 0 { +func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { + if len(db.uri.Schema) == 0 { args := []interface{}{tableName, idxName} return `SELECT indexname FROM pg_indexes WHERE tablename = ? AND indexname = ?`, args } - args := []interface{}{db.Schema, tableName, idxName} + args := []interface{}{db.uri.Schema, tableName, idxName} return `SELECT indexname FROM pg_indexes ` + `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } -func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { - if len(db.Schema) == 0 { +func (db *postgres) TableCheckSQL(tableName string) (string, []interface{}) { + if len(db.uri.Schema) == 0 { args := []interface{}{tableName} return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args } - args := []interface{}{db.Schema, tableName} + args := []interface{}{db.uri.Schema, tableName} return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args } -func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { - if len(db.Schema) == 0 || strings.Contains(tableName, ".") { +func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { + if len(db.uri.Schema) == 0 || strings.Contains(tableName, ".") { return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", - tableName, col.Name, db.SqlType(col)) + tableName, col.Name, db.SQLType(col)) } return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s", - db.Schema, tableName, col.Name, db.SqlType(col)) + db.uri.Schema, tableName, col.Name, db.SQLType(col)) } -func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { +func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string { quote := db.Quote idxName := index.Name @@ -918,23 +919,23 @@ func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) } else { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } } - if db.Uri.Schema != "" { - idxName = db.Uri.Schema + "." + idxName + if db.uri.Schema != "" { + idxName = db.uri.Schema + "." + idxName } return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { - args := []interface{}{db.Schema, tableName, colName} + args := []interface{}{db.uri.Schema, tableName, colName} query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" + " AND column_name = $3" - if len(db.Schema) == 0 { + if len(db.uri.Schema) == 0 { args = []interface{}{tableName, colName} query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + " AND column_name = $2" @@ -950,7 +951,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { return rows.Next(), nil } -func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *postgres) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, @@ -965,8 +966,8 @@ FROM pg_attribute f WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;` var f string - if len(db.Schema) != 0 { - args = append(args, db.Schema) + if len(db.uri.Schema) != 0 { + args = append(args, db.uri.Schema) f = " AND s.table_schema = $2" } s = fmt.Sprintf(s, f) @@ -979,11 +980,11 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) var colName, isNullable, dataType string @@ -1023,23 +1024,23 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att switch dataType { case "character varying", "character": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 0, DefaultLength2: 0} case "timestamp without time zone": - col.SQLType = core.SQLType{Name: core.DateTime, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0} case "timestamp with time zone": - col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "double precision": - col.SQLType = core.SQLType{Name: core.Double, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Double, DefaultLength: 0, DefaultLength2: 0} case "boolean": - col.SQLType = core.SQLType{Name: core.Bool, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Bool, DefaultLength: 0, DefaultLength2: 0} case "time without time zone": - col.SQLType = core.SQLType{Name: core.Time, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Time, DefaultLength: 0, DefaultLength2: 0} case "oid": - col.SQLType = core.SQLType{Name: core.BigInt, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.BigInt, DefaultLength: 0, DefaultLength2: 0} default: - col.SQLType = core.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} } - if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { return nil, nil, fmt.Errorf("Unknown colType: %v", dataType) } @@ -1065,11 +1066,11 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att return colSeq, cols, nil } -func (db *postgres) GetTables() ([]*core.Table, error) { +func (db *postgres) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT tablename FROM pg_tables" - if len(db.Schema) != 0 { - args = append(args, db.Schema) + if len(db.uri.Schema) != 0 { + args = append(args, db.uri.Schema) s = s + " WHERE schemaname = $1" } @@ -1081,9 +1082,9 @@ func (db *postgres) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) if err != nil { @@ -1106,11 +1107,11 @@ func getIndexColName(indexdef string) []string { return colNames } -func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *postgres) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1") - if len(db.Schema) != 0 { - args = append(args, db.Schema) + if len(db.uri.Schema) != 0 { + args = append(args, db.uri.Schema) s = s + " AND schemaname=$2" } db.LogSQL(s, args) @@ -1121,7 +1122,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, indexdef string @@ -1135,9 +1136,9 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) continue } if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") { - indexType = core.UniqueType + indexType = schemas.UniqueType } else { - indexType = core.IndexType + indexType = schemas.IndexType } colNames = getIndexColName(indexdef) var isRegular bool @@ -1149,7 +1150,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) } } - index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} + index := &schemas.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} for _, colName := range colNames { index.Cols = append(index.Cols, strings.Trim(colName, `" `)) } @@ -1159,8 +1160,8 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) return indexes, nil } -func (db *postgres) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{Prefix: "$", Start: 1}} +func (db *postgres) Filters() []Filter { + return []Filter{&IdFilter{}, &QuoteFilter{}, &SeqFilter{Prefix: "$", Start: 1}} } type pqDriver struct { @@ -1214,12 +1215,12 @@ func parseOpts(name string, o values) error { return nil } -func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.POSTGRES} +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://") { - db.DbName, err = parseURL(dataSourceName) + db.DBName, err = parseURL(dataSourceName) if err != nil { return nil, err } @@ -1230,10 +1231,10 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { return nil, err } - db.DbName = o.Get("dbname") + db.DBName = o.Get("dbname") } - if db.DbName == "" { + if db.DBName == "" { return nil, errors.New("dbname is empty") } @@ -1244,7 +1245,7 @@ type pqDriverPgx struct { pqDriver } -func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*URI, error) { // Remove the leading characters for driver to work if len(dataSourceName) >= 9 && dataSourceName[0] == 0 { dataSourceName = dataSourceName[9:] diff --git a/dialect_postgres_test.go b/dialects/postgres_test.go similarity index 92% rename from dialect_postgres_test.go rename to dialects/postgres_test.go index f2afdefc..c0a8eb6f 100644 --- a/dialect_postgres_test.go +++ b/dialects/postgres_test.go @@ -1,11 +1,10 @@ -package xorm +package dialects import ( "reflect" "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" ) func TestParsePostgres(t *testing.T) { @@ -27,15 +26,15 @@ func TestParsePostgres(t *testing.T) { {"dbname=db =disable", "db", false}, } - driver := core.QueryDriver("postgres") + driver := QueryDriver("postgres") for _, test := range tests { 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) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } } } @@ -59,23 +58,23 @@ func TestParsePgx(t *testing.T) { {"dbname=db =disable", "db", false}, } - driver := core.QueryDriver("pgx") + driver := QueryDriver("pgx") for _, test := range tests { uri, err := driver.Parse("pgx", 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) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } // Register DriverConfig uri, err = driver.Parse("pgx", 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) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } } diff --git a/dialect_sqlite3.go b/dialects/sqlite3.go similarity index 83% rename from dialect_sqlite3.go rename to dialects/sqlite3.go index 0a290f3c..b7ff2147 100644 --- a/dialect_sqlite3.go +++ b/dialects/sqlite3.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "database/sql" @@ -11,7 +11,8 @@ import ( "regexp" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -144,42 +145,42 @@ var ( ) type sqlite3 struct { - core.Base + Base } -func (db *sqlite3) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *sqlite3) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (db *sqlite3) SqlType(c *core.Column) string { +func (db *sqlite3) SQLType(c *schemas.Column) string { switch t := c.SQLType.Name; t { - case core.Bool: + case schemas.Bool: if c.Default == "true" { c.Default = "1" } else if c.Default == "false" { c.Default = "0" } - return core.Integer - case core.Date, core.DateTime, core.TimeStamp, core.Time: - return core.DateTime - case core.TimeStampz: - return core.Text - case core.Char, core.Varchar, core.NVarchar, core.TinyText, - core.Text, core.MediumText, core.LongText, core.Json: - return core.Text - case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt: - return core.Integer - case core.Float, core.Double, core.Real: - return core.Real - case core.Decimal, core.Numeric: - return core.Numeric - case core.TinyBlob, core.Blob, core.MediumBlob, core.LongBlob, core.Bytea, core.Binary, core.VarBinary: - return core.Blob - case core.Serial, core.BigSerial: + return schemas.Integer + case schemas.Date, schemas.DateTime, schemas.TimeStamp, schemas.Time: + return schemas.DateTime + case schemas.TimeStampz: + return schemas.Text + 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: + return schemas.Integer + case schemas.Float, schemas.Double, schemas.Real: + return schemas.Real + case schemas.Decimal, schemas.Numeric: + return schemas.Numeric + case schemas.TinyBlob, schemas.Blob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea, schemas.Binary, schemas.VarBinary: + return schemas.Blob + case schemas.Serial, schemas.BigSerial: c.IsPrimaryKey = true c.IsAutoIncrement = true c.Nullable = false - return core.Integer + return schemas.Integer default: return t } @@ -218,24 +219,24 @@ func (db *sqlite3) IndexOnTable() bool { return false } -func (db *sqlite3) IndexCheckSql(tableName, idxName string) (string, []interface{}) { +func (db *sqlite3) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args } -func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) { +func (db *sqlite3) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{tableName} return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args } -func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { +func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { // var unique string quote := db.Quote idxName := index.Name if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) } else { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) @@ -244,7 +245,7 @@ func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } -func (db *sqlite3) ForUpdateSql(query string) string { +func (db *sqlite3) ForUpdateSQL(query string) string { return query } @@ -298,9 +299,9 @@ func splitColStr(colStr string) []string { return results } -func parseString(colStr string) (*core.Column, error) { +func parseString(colStr string) (*schemas.Column, error) { fields := splitColStr(colStr) - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) col.Nullable = true col.DefaultIsEmpty = true @@ -310,7 +311,7 @@ func parseString(colStr string) (*core.Column, error) { col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`) continue } else if idx == 1 { - col.SQLType = core.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0} continue } switch field { @@ -332,7 +333,7 @@ func parseString(colStr string) (*core.Column, error) { return col, nil } -func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" db.LogSQL(s, args) @@ -359,7 +360,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu nEnd := strings.LastIndex(name, ")") reg := regexp.MustCompile(`[^\(,\)]*(\([^\(]*\))?`) colCreates := reg.FindAllString(name[nStart+1:nEnd], -1) - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for _, colStr := range colCreates { @@ -389,7 +390,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu return colSeq, cols, nil } -func (db *sqlite3) GetTables() ([]*core.Table, error) { +func (db *sqlite3) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT name FROM sqlite_master WHERE type='table'" db.LogSQL(s, args) @@ -400,9 +401,9 @@ func (db *sqlite3) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() err = rows.Scan(&table.Name) if err != nil { return nil, err @@ -415,7 +416,7 @@ func (db *sqlite3) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *sqlite3) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" db.LogSQL(s, args) @@ -426,7 +427,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var tmpSQL sql.NullString err = rows.Scan(&tmpSQL) @@ -439,7 +440,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } sql := tmpSQL.String - index := new(core.Index) + index := new(schemas.Index) nNStart := strings.Index(sql, "INDEX") nNEnd := strings.Index(sql, "ON") if nNStart == -1 || nNEnd == -1 { @@ -456,9 +457,9 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { - index.Type = core.UniqueType + index.Type = schemas.UniqueType } else { - index.Type = core.IndexType + index.Type = schemas.IndexType } nStart := strings.Index(sql, "(") @@ -476,17 +477,17 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) return indexes, nil } -func (db *sqlite3) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}} +func (db *sqlite3) Filters() []Filter { + return []Filter{&IdFilter{}} } type sqlite3Driver struct { } -func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) { if strings.Contains(dataSourceName, "?") { dataSourceName = dataSourceName[:strings.Index(dataSourceName, "?")] } - return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil + return &URI{DBType: schemas.SQLITE, DBName: dataSourceName}, nil } diff --git a/dialect_sqlite3_test.go b/dialects/sqlite3_test.go similarity index 97% rename from dialect_sqlite3_test.go rename to dialects/sqlite3_test.go index a2036159..aa6c3cea 100644 --- a/dialect_sqlite3_test.go +++ b/dialects/sqlite3_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "testing" diff --git a/doc.go b/doc.go index 9620bca1..8df4fb30 100644 --- a/doc.go +++ b/doc.go @@ -126,7 +126,7 @@ Attention: the above 8 methods should be the last chainable method. engine.ID(1).Get(&user) // for single primary key // SELECT * FROM user WHERE id = 1 - engine.ID(core.PK{1, 2}).Get(&user) // for composite primary keys + 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) diff --git a/docs/images/cache_design.graffle b/docs/images/cache_design.graffle deleted file mode 100644 index 5b7c487b..00000000 --- a/docs/images/cache_design.graffle +++ /dev/null @@ -1,2295 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {771, 554.18930041152259}} - Class - SolidGraphic - ID - 2 - Style - - fill - - Color - - b - 0.989303 - g - 0.907286 - r - 0.795377 - - FillType - 2 - GradientAngle - 78 - GradientColor - - b - 1 - g - 0.854588 - r - 0.623912 - - MiddleColor - - b - 1 - g - 0.856844 - r - 0.43695 - - TrippleBlend - YES - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - CanvasSize - {771, 554.18930041152259} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2013-09-29 07:57:57 +0000 - Creator - Lunny Xiao - DisplayScale - 1.000 cm = 1.000 cm - FileType - flat - GraphDocumentVersion - 8 - GraphicsList - - - Bounds - {{409.89504441572683, 415.64570506990464}, {104.42639923095703, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 30 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 .\ -.\ -.\ -} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{276.44083898205287, 413.07252538018992}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 29 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 .\ -.\ -.} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{409.89504441572689, 337.17180246145824}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 28 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-2:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{274.32251833907753, 322.94787397618234}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 27 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb3:[2,5]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08888702072045, 256.42244420026987}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 25 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-2:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08888302813585, 187.47137690331695}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 24 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 table-1:Table\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08887903555114, 118.52029512620169}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 23 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-1:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54055354053583, 325.93280718390133}, {124.41892177446698, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 22 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Del(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54055354053583, 240.81081643580876}, {124.41892177446698, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 21 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Put(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54054526129187, 150.52220626433376}, {124.41893005371094, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 20 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Get(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{276.44083898205287, 214.72973288487913}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 19 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb2:[2,5]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{274.32251963877655, 117.18245433711769}, {112.36092376708984, 68.777008056640625}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 18 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb1:[1,2,3]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{103.00000194954862, 30.108108889738791}, {80, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 14 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{532.04631600645348, 25.096524339927051}, {166.93052673339844, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 13 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;\f1\fnil\fcharset134 STHeitiSC-Light;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Cache\ - -\f1 Store} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{298.9845516069733, 37.412179328792348}, {173.03089904785156, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 17 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 LRUCacher} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{99.822519035537013, 333.12161552787364}, {88, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - NSKern - 0.0 - Size - 15 - - ID - 12 - Magnets - - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.806569 - g - 0.806569 - r - 0.806569 - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.653285 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.2 - g - 0.2 - r - 0.2 - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc - -\f0\fs30 \cf0 \expnd0\expndtw0\kerning0 -Delet\ -SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{97.118322659032714, 226.09466078541047}, {88, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0 - g - 0 - r - 0.501961 - - Font - Verdana - NSKern - 0.0 - Size - 15 - - ID - 15 - Magnets - - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0 - g - 0.389485 - r - 1 - - FillType - 3 - GradientCenter - {-0.34285700000000002, -0.114286} - GradientColor - - b - 0 - g - 0.495748 - r - 1 - - MiddleColor - - b - 0 - g - 0.887657 - r - 1 - - MiddleFraction - 0.6269841194152832 - TrippleBlend - YES - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.2 - g - 0.2 - r - 0.2 - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red128\green0\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc - -\f0\fs30 \cf2 \expnd0\expndtw0\kerning0 -Update\ -SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{103, 123.08108395608006}, {80, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.821332 - g - 0.672602 - r - 0.928374 - - Font - Verdana - Size - 18 - - ID - 16 - Shape - Rectangle - Style - - fill - - Color - - b - 0.436973 - g - 0.155566 - r - 0.758999 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.25098 - g - 0 - r - 0.501961 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;\f1\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red237\green172\blue209;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 select -\f1 -\f0 SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 2 - ImageCounter - 3 - KeepToScale - - Layers - - - Lock - NO - Name - 图层 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - AutoLayout - 2 - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - neato - neatoLineLength - 0.92083334922790527 - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-09-29 08:24:57 +0000 - Modifier - Lunny Xiao - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - OutlineStyle - Brainstorming/Clouds - PageBreaks - NO - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {595, 842} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - 版面 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - FitInWindow - - Frame - {{138, 197}, {869, 617}} - ListView - - OutlineWidth - 142 - RightSidebar - - Sidebar - - SidebarWidth - 138 - VisibleRegion - {{1.0591603214876664, 1.0591603214876664}, {770.00955372153339, 553.94084813804955}} - Zoom - 0.94414412975311279 - ZoomValues - - - 版面 1 - 0.0 - 1 - - - - - diff --git a/docs/images/cache_design.png b/docs/images/cache_design.png deleted file mode 100644 index 11ce817663c8941c266abdd4dae450c277233420..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243596 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
Q}&q4+j^^$+5QTmA+dnUnvir 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

E!EASHl6N8}Ct(#6gt)wqlE`eGY_> zzE0+9xWJnEuBaoo8J@$LWY(c`w}EV>Q;EohUAZvoL-1a-Jw&m1@JGxjkU#n?Jze`X z`ydNJRQ+W45;U*!)lwTt4F3q?Q>D$R&9;(rj><^1@22o4A0Wq;j@Em ze&XM^lcW+KH%3L3%PZV=Pk(Rd=T5 zcfC^kq@&boM&>zDwP}n-#BHRUbTs}!Ess%cfGN4dcd39P#(NVkH)Jh<+jCOV>P`Pd zIIJGU?)%IE_dBXmi2G+~+~~BQU&sQ)u+el+MN#R)&&LpjYy(9D1q^6Sh%AbP zJT*#KV-IA2La%l$%20>OhE$-W@S2zEIsuGT(ZU5#wrzSxf2qEwkee7IAJO*{+-?3=ZF175iI z$K>6iiXekyZtMW#)US31u)8Bh6!!bJ6lf^565JeRFUd}vvNOQ*v zs^O8bI8Z0Z`s>JO|69nOQJnyT0O!jt+6&(^Q-$|xl*0JF5`Yo#?`d{9pj^}qzwpPh|n-d5ryw%bzA&dw^r+!JbCo@AQCV0B_SlN%V9tY6^^*=&Nq?zbL4c)c9WpLETXJsrhZv7H9PzT7J-A_@AYGs*L z@fLSi@p>l2J6nD~Tz0B5_ncyH*OL-`5%-jyx8C*RHP?f&zwiTG&rwrV0_o`xG31$! zYTBOTc%n;eSfHmBpaJtvo&vOxgeO`()SO~zMRJshczAyxy(k;lMQ(G+@sw#K>yMwR z%xZ$2xlpVfdrA*$X8G|T^4dvsUYEH;VZ*zu-T`Gv z+JkKEI>TijQ~oOL_E37bF($V1mH}D+n^Dr&)IMJ>*(`bRI5 zO_%%sPP0I%P)yiY1a9FtRs0u_{771pvh#BXoxJTEcMmd40PmPUG6R@Z8UOsr7rUZU_DX3ii)#{(L){wSBF_GivJ}qM!ImtLM=yYfb4t z&=Yzp_S|%XYlSYEQ1WF0JJp_lYIm`&7b-FDK=J0{bQ?`}7cIYa%0mEbSf8TEtdsH= zo(wv|~dlvONz~@KF&xI@rxcOUXI=I1{Qem}o7KYcAiZ<} z{l|Un&ONQ=3W}mvPglPD*{fQRmEU{byYXhl#kgs8$nVxVw?@Bv)+bywJHoGZNKI!V z7W&&`_s@iN*3VUMJf~QHB(bdRA9+rAoVJ|N2SDZf0tO}=abaTt%nHS7I&gMXG${E+ zzvM~Ap23PxFh$Hz!$o-x-0!dGWV(p+@qFZXSe2n}+!R*}w@%|hGHmZBfjqb6-}Kv6 z-lSu`OZ)1_^|A|{aOU@6YiwX&4nOH~uv6D^g0^VhDeTE|JKPw}&xDm=j3@@q@&p}V zz`Y#7lxz9*1-b7+ql`uyfUxfHxrJ_(q+YCkNuMo<7z<3ORRmi2?Ia zniZc8`zYhEwW|JnF=Ft9*#%t(kJ|N2+HJc_>okejTUnqFjZ&FzzO3qDt#3z3fpVX^ zPUtGb>>`hkx7u*s|5M%b%YQEM(HAVX&)S^79!qvwV2G z(ZPkDo^# 0)) } else { temp += fmt.Sprintf(", %v", d) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if col.SQLType.Name == core.Bool { + if col.SQLType.Name == schemas.Bool { temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Uint() > 0)) } else { temp += fmt.Sprintf(", %v", d) @@ -611,7 +616,7 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D } // FIXME: Hack for postgres - if string(dialect.DBType()) == core.POSTGRES && table.AutoIncrColumn() != nil { + if string(dialect.DBType()) == schemas.POSTGRES && table.AutoIncrColumn() != nil { _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quote(table.Name)+"), 1), false);\n") if err != nil { return err @@ -856,7 +861,7 @@ func (engine *Engine) UnMapType(t reflect.Type) { delete(engine.Tables, t) } -func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) { +func (engine *Engine) autoMapType(v reflect.Value) (*schemas.Table, error) { t := v.Type() engine.mutex.Lock() defer engine.mutex.Unlock() @@ -888,7 +893,7 @@ func (engine *Engine) GobRegister(v interface{}) *Engine { // Table table struct type Table struct { - *core.Table + *schemas.Table Name string } @@ -907,12 +912,12 @@ func (engine *Engine) TableInfo(bean interface{}) *Table { return &Table{tb, engine.TableName(bean)} } -func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) { +func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) { if index, ok := table.Indexes[indexName]; ok { index.AddColumn(col.Name) col.Indexes[index.Name] = indexType } else { - index := core.NewIndex(indexName, indexType) + index := schemas.NewIndex(indexName, indexType) index.AddColumn(col.Name) table.AddIndex(index) col.Indexes[index.Name] = indexType @@ -928,11 +933,11 @@ var ( tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() ) -func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { +func (engine *Engine) mapType(v reflect.Value) (*schemas.Table, error) { t := v.Type() - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() table.Type = t - table.Name = getTableName(engine.TableMapper, v) + table.Name = names.GetTableName(engine.TableMapper, v) var idFieldColName string var hasCacheTag, hasNoCacheTag bool @@ -941,17 +946,17 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) - var col *core.Column + var col *schemas.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() if ormTagStr != "" { - col = &core.Column{ + col = &schemas.Column{ FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, IsAutoIncrement: false, - MapType: core.TWOSIDES, + MapType: schemas.TWOSIDES, Indexes: make(map[string]int), DefaultIsEmpty: true, } @@ -1039,9 +1044,9 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { } if col.SQLType.Name == "" { - col.SQLType = core.Type2SQLType(fieldType) + col.SQLType = schemas.Type2SQLType(fieldType) } - engine.dialect.SqlType(col) + engine.dialect.SQLType(col) if col.Length == 0 { col.Length = col.SQLType.DefaultLength } @@ -1053,9 +1058,9 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { } if ctx.isUnique { - ctx.indexNames[col.Name] = core.UniqueType + ctx.indexNames[col.Name] = schemas.UniqueType } else if ctx.isIndex { - ctx.indexNames[col.Name] = core.IndexType + ctx.indexNames[col.Name] = schemas.IndexType } for indexName, indexType := range ctx.indexNames { @@ -1063,18 +1068,18 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { } } } else { - var sqlType core.SQLType + var sqlType schemas.SQLType if fieldValue.CanAddr() { - if _, ok := fieldValue.Addr().Interface().(core.Conversion); ok { - sqlType = core.SQLType{Name: core.Text} + if _, ok := fieldValue.Addr().Interface().(Conversion); ok { + sqlType = schemas.SQLType{Name: schemas.Text} } } - if _, ok := fieldValue.Interface().(core.Conversion); ok { - sqlType = core.SQLType{Name: core.Text} + if _, ok := fieldValue.Interface().(Conversion); ok { + sqlType = schemas.SQLType{Name: schemas.Text} } else { - sqlType = core.Type2SQLType(fieldType) + sqlType = schemas.Type2SQLType(fieldType) } - col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), + col = schemas.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, sqlType.DefaultLength, sqlType.DefaultLength2, true) @@ -1105,7 +1110,7 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { engine.setCacher(table.Name, engine.Cacher) } else { engine.logger.Info("enable LRU cache on table:", table.Name) - engine.setCacher(table.Name, NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)) + engine.setCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) } } if hasNoCacheTag { @@ -1133,24 +1138,24 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { // IdOf get id from one struct // // Deprecated: use IDOf instead. -func (engine *Engine) IdOf(bean interface{}) core.PK { +func (engine *Engine) IdOf(bean interface{}) schemas.PK { return engine.IDOf(bean) } // IDOf get id from one struct -func (engine *Engine) IDOf(bean interface{}) core.PK { +func (engine *Engine) IDOf(bean interface{}) schemas.PK { return engine.IdOfV(reflect.ValueOf(bean)) } // IdOfV get id from one value of struct // // Deprecated: use IDOfV instead. -func (engine *Engine) IdOfV(rv reflect.Value) core.PK { +func (engine *Engine) IdOfV(rv reflect.Value) schemas.PK { return engine.IDOfV(rv) } // IDOfV get id from one value of struct -func (engine *Engine) IDOfV(rv reflect.Value) core.PK { +func (engine *Engine) IDOfV(rv reflect.Value) schemas.PK { pk, err := engine.idOfV(rv) if err != nil { engine.logger.Error(err) @@ -1159,7 +1164,7 @@ func (engine *Engine) IDOfV(rv reflect.Value) core.PK { return pk } -func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) { +func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) { v := reflect.Indirect(rv) table, err := engine.autoMapType(v) if err != nil { @@ -1202,10 +1207,10 @@ func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) { return nil, err } } - return core.PK(pk), nil + return schemas.PK(pk), nil } -func (engine *Engine) idTypeAssertion(col *core.Column, sid string) (interface{}, error) { +func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) { if col.SQLType.IsNumeric() { n, err := strconv.ParseInt(sid, 10, 64) if err != nil { @@ -1317,7 +1322,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { if err := session.statement.setRefBean(bean); err != nil { return err } - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true) if err != nil { return err @@ -1332,7 +1337,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } } - } else if index.Type == core.IndexType { + } else if index.Type == schemas.IndexType { isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false) if err != nil { return err @@ -1601,7 +1606,7 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { } // nowTime return current time -func (engine *Engine) nowTime(col *core.Column) (interface{}, time.Time) { +func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) { t := time.Now() var tz = engine.DatabaseTZ if !col.DisableTimeZone && col.TimeZone != nil { @@ -1610,7 +1615,7 @@ func (engine *Engine) nowTime(col *core.Column) (interface{}, time.Time) { return engine.formatTime(col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) } -func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) { +func (engine *Engine) formatColTime(col *schemas.Column, t time.Time) (v interface{}) { if t.IsZero() { if col.Nullable { return nil @@ -1627,20 +1632,20 @@ func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{ // formatTime format time as column type func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{}) { switch sqlTypeName { - case core.Time: + case schemas.Time: s := t.Format("2006-01-02 15:04:05") // time.RFC3339 v = s[11:19] - case core.Date: + case schemas.Date: v = t.Format("2006-01-02") - case core.DateTime, core.TimeStamp, core.Varchar: // !DarthPestilane! format time when sqlTypeName is core.Varchar. + case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. v = t.Format("2006-01-02 15:04:05") - case core.TimeStampz: - if engine.dialect.DBType() == core.MSSQL { + case schemas.TimeStampz: + if engine.dialect.DBType() == schemas.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") } else { v = t.Format(time.RFC3339Nano) } - case core.BigInt, core.Int: + case schemas.BigInt, schemas.Int: v = t.Unix() default: v = t @@ -1649,12 +1654,12 @@ func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{} } // GetColumnMapper returns the column name mapper -func (engine *Engine) GetColumnMapper() core.IMapper { +func (engine *Engine) GetColumnMapper() names.Mapper { return engine.ColumnMapper } // GetTableMapper returns the table name mapper -func (engine *Engine) GetTableMapper() core.IMapper { +func (engine *Engine) GetTableMapper() names.Mapper { return engine.TableMapper } diff --git a/engine_cond.go b/engine_cond.go index 17f50bd7..480e7ded 100644 --- a/engine_cond.go +++ b/engine_cond.go @@ -12,10 +12,10 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) -func (engine *Engine) buildConds(table *core.Table, bean interface{}, +func (engine *Engine) buildConds(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) { @@ -31,7 +31,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, continue } - if engine.dialect.DBType() == core.MSSQL && (col.SQLType.Name == core.Text || col.SQLType.IsBlob() || col.SQLType.Name == core.TimeStampz) { + if engine.dialect.DBType() == schemas.MSSQL && (col.SQLType.Name == schemas.Text || col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { continue } if col.SQLType.IsJson() { @@ -130,13 +130,13 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, t := int64(fieldValue.Uint()) val = reflect.ValueOf(&t).Interface() case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } val = engine.formatColTime(col, t) - } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok { + } else if _, ok := reflect.New(fieldType).Interface().(Conversion); ok { continue } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = valNul.Value() diff --git a/engine_group.go b/engine_group.go index 42d49eca..24dc0103 100644 --- a/engine_group.go +++ b/engine_group.go @@ -8,7 +8,9 @@ import ( "context" "time" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/log" + "xorm.io/xorm/names" ) // EngineGroup defines an engine group @@ -109,7 +111,7 @@ func (eg *EngineGroup) Ping() error { } // SetColumnMapper set the column name mapping rule -func (eg *EngineGroup) SetColumnMapper(mapper core.IMapper) { +func (eg *EngineGroup) SetColumnMapper(mapper names.Mapper) { eg.Engine.ColumnMapper = mapper for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].ColumnMapper = mapper @@ -125,7 +127,7 @@ func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) { } // SetDefaultCacher set the default cacher -func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { +func (eg *EngineGroup) SetDefaultCacher(cacher caches.Cacher) { eg.Engine.SetDefaultCacher(cacher) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetDefaultCacher(cacher) @@ -133,7 +135,7 @@ func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { } // SetLogger set the new logger -func (eg *EngineGroup) SetLogger(logger core.ILogger) { +func (eg *EngineGroup) SetLogger(logger log.Logger) { eg.Engine.SetLogger(logger) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetLogger(logger) @@ -141,7 +143,7 @@ func (eg *EngineGroup) SetLogger(logger core.ILogger) { } // SetLogLevel sets the logger level -func (eg *EngineGroup) SetLogLevel(level core.LogLevel) { +func (eg *EngineGroup) SetLogLevel(level log.LogLevel) { eg.Engine.SetLogLevel(level) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetLogLevel(level) @@ -149,7 +151,7 @@ func (eg *EngineGroup) SetLogLevel(level core.LogLevel) { } // SetMapper set the name mapping rules -func (eg *EngineGroup) SetMapper(mapper core.IMapper) { +func (eg *EngineGroup) SetMapper(mapper names.Mapper) { eg.Engine.SetMapper(mapper) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetMapper(mapper) @@ -179,7 +181,7 @@ func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup { } // SetTableMapper set the table name mapping rule -func (eg *EngineGroup) SetTableMapper(mapper core.IMapper) { +func (eg *EngineGroup) SetTableMapper(mapper names.Mapper) { eg.Engine.TableMapper = mapper for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].TableMapper = mapper diff --git a/engine_table.go b/engine_table.go index 001d72e7..e60d8fce 100644 --- a/engine_table.go +++ b/engine_table.go @@ -9,16 +9,18 @@ import ( "reflect" "strings" - "xorm.io/core" + "xorm.io/xorm/dialects" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) // tbNameWithSchema will automatically add schema prefix on table name func (engine *Engine) tbNameWithSchema(v string) string { // Add schema name as prefix of table name. // Only for postgres database. - if engine.dialect.DBType() == core.POSTGRES && + if engine.dialect.DBType() == schemas.POSTGRES && engine.dialect.URI().Schema != "" && - engine.dialect.URI().Schema != postgresPublicSchema && + engine.dialect.URI().Schema != dialects.PostgresPublicSchema && strings.Index(v, ".") == -1 { return engine.dialect.URI().Schema + "." + v } @@ -44,7 +46,7 @@ func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string } // tbName get some table's table name -func (session *Session) tbNameNoSchema(table *core.Table) string { +func (session *Session) tbNameNoSchema(table *schemas.Table) string { if len(session.statement.AltTableName) > 0 { return session.statement.AltTableName } @@ -76,7 +78,7 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { v := rValue(f) t := v.Type() if t.Kind() == reflect.Struct { - table = getTableName(engine.TableMapper, v) + table = names.GetTableName(engine.TableMapper, v) } else { table = engine.Quote(fmt.Sprintf("%v", f)) } @@ -94,12 +96,12 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { return tablename.(string) case reflect.Value: v := tablename.(reflect.Value) - return getTableName(engine.TableMapper, v) + return names.GetTableName(engine.TableMapper, v) default: v := rValue(tablename) t := v.Type() if t.Kind() == reflect.Struct { - return getTableName(engine.TableMapper, v) + return names.GetTableName(engine.TableMapper, v) } return engine.Quote(fmt.Sprintf("%v", tablename)) } diff --git a/examples/cache/cache.go b/examples/cache/cache.go index ba756813..4ac8eedf 100644 --- a/examples/cache/cache.go +++ b/examples/cache/cache.go @@ -6,6 +6,7 @@ import ( _ "github.com/mattn/go-sqlite3" "xorm.io/xorm" + "xorm.io/xorm/caches" ) // User describes a user @@ -15,7 +16,7 @@ type User struct { } func main() { - f := "cache.db" + f := "caches.db" os.Remove(f) Orm, err := xorm.NewEngine("sqlite3", f) @@ -24,7 +25,7 @@ func main() { return } Orm.ShowSQL(true) - cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 1000) Orm.SetDefaultCacher(cacher) err = Orm.CreateTables(&User{}) diff --git a/examples/cache_gorountine/cache_goroutine.go b/examples/cache_gorountine/cache_goroutine.go index 94b29e82..3543cba8 100644 --- a/examples/cache_gorountine/cache_goroutine.go +++ b/examples/cache_gorountine/cache_goroutine.go @@ -8,6 +8,7 @@ import ( _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" "xorm.io/xorm" + "xorm.io/xorm/caches" ) // User describes a user @@ -87,7 +88,7 @@ func main() { return } engine.ShowSQL(true) - cacher := xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000) engine.SetDefaultCacher(cacher) fmt.Println(engine) test(engine) @@ -97,7 +98,7 @@ func main() { fmt.Println("-----start mysql go routines-----") engine, err = mysqlEngine() engine.ShowSQL(true) - cacher = xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000) + cacher = caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000) engine.SetDefaultCacher(cacher) if err != nil { fmt.Println(err) diff --git a/go.mod b/go.mod index 6d8b58f4..07bccdc6 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,6 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 github.com/stretchr/testify v1.4.0 github.com/ziutek/mymysql v1.5.4 + google.golang.org/appengine v1.6.0 // indirect xorm.io/builder v0.3.6 - xorm.io/core v0.7.2 ) diff --git a/go.sum b/go.sum index 2102cc5b..4666c8ae 100644 --- a/go.sum +++ b/go.sum @@ -145,5 +145,3 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= -xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= -xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= diff --git a/helpers.go b/helpers.go index aadeb6dc..b7c583f7 100644 --- a/helpers.go +++ b/helpers.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // str2PK convert string value to primary key value according to tp @@ -95,26 +95,6 @@ func str2PK(s string, tp reflect.Type) (interface{}, error) { return v.Interface(), nil } -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 - } - } - } - if lastIdx < len(tag) { - tags = append(tags, strings.TrimSpace(tag[lastIdx:])) - } - return -} - type zeroable interface { IsZero() bool } @@ -249,7 +229,7 @@ func int64ToInt(id int64, tp reflect.Type) interface{} { return int64ToIntValue(id, tp).Interface() } -func isPKZero(pk core.PK) bool { +func isPKZero(pk schemas.PK) bool { for _, k := range pk { if isZero(k) { return true diff --git a/interface.go b/interface.go index 81a4b68a..43b4b5f4 100644 --- a/interface.go +++ b/interface.go @@ -10,7 +10,11 @@ import ( "reflect" "time" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/dialects" + "xorm.io/xorm/log" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) // Interface defines the interface which Engine, EngineGroup and Session will implementate. @@ -76,31 +80,31 @@ type EngineInterface interface { ClearCache(...interface{}) error Context(context.Context) *Session CreateTables(...interface{}) error - DBMetas() ([]*core.Table, error) - Dialect() core.Dialect + DBMetas() ([]*schemas.Table, error) + Dialect() dialects.Dialect DropTables(...interface{}) error - DumpAllToFile(fp string, tp ...core.DbType) error - GetCacher(string) core.Cacher - GetColumnMapper() core.IMapper - GetDefaultCacher() core.Cacher - GetTableMapper() core.IMapper + DumpAllToFile(fp string, tp ...dialects.DBType) error + GetCacher(string) caches.Cacher + GetColumnMapper() names.Mapper + GetDefaultCacher() caches.Cacher + GetTableMapper() names.Mapper GetTZDatabase() *time.Location GetTZLocation() *time.Location - MapCacher(interface{}, core.Cacher) error + MapCacher(interface{}, caches.Cacher) error NewSession() *Session NoAutoTime() *Session Quote(string) string - SetCacher(string, core.Cacher) + SetCacher(string, caches.Cacher) SetConnMaxLifetime(time.Duration) - SetColumnMapper(core.IMapper) - SetDefaultCacher(core.Cacher) - SetLogger(logger core.ILogger) - SetLogLevel(core.LogLevel) - SetMapper(core.IMapper) + SetColumnMapper(names.Mapper) + SetDefaultCacher(caches.Cacher) + SetLogger(logger log.Logger) + SetLogLevel(log.LogLevel) + SetMapper(names.Mapper) SetMaxOpenConns(int) SetMaxIdleConns(int) SetSchema(string) - SetTableMapper(core.IMapper) + SetTableMapper(names.Mapper) SetTZDatabase(tz *time.Location) SetTZLocation(tz *time.Location) ShowExecTime(...bool) diff --git a/logger.go b/log/logger.go similarity index 67% rename from logger.go rename to log/logger.go index 7b26e77f..b5ab9019 100644 --- a/logger.go +++ b/log/logger.go @@ -2,26 +2,56 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package log import ( "fmt" "io" "log" +) - "xorm.io/core" +// LogLevel defines a log level +type LogLevel int + +// enumerate all LogLevels +const ( + // !nashtsai! following level also match syslog.Priority value + LOG_DEBUG LogLevel = iota + LOG_INFO + LOG_WARNING + LOG_ERR + LOG_OFF + LOG_UNKNOWN ) // default log options const ( DEFAULT_LOG_PREFIX = "[xorm]" DEFAULT_LOG_FLAG = log.Ldate | log.Lmicroseconds - DEFAULT_LOG_LEVEL = core.LOG_DEBUG + DEFAULT_LOG_LEVEL = LOG_DEBUG ) -var _ core.ILogger = DiscardLogger{} +// Logger is a logger interface +type Logger interface { + Debug(v ...interface{}) + Debugf(format string, v ...interface{}) + Error(v ...interface{}) + Errorf(format string, v ...interface{}) + Info(v ...interface{}) + Infof(format string, v ...interface{}) + Warn(v ...interface{}) + Warnf(format string, v ...interface{}) -// DiscardLogger don't log implementation for core.ILogger + Level() LogLevel + SetLevel(l LogLevel) + + ShowSQL(show ...bool) + IsShowSQL() bool +} + +var _ Logger = DiscardLogger{} + +// DiscardLogger don't log implementation for ILogger type DiscardLogger struct{} // Debug empty implementation @@ -49,12 +79,12 @@ func (DiscardLogger) Warn(v ...interface{}) {} func (DiscardLogger) Warnf(format string, v ...interface{}) {} // Level empty implementation -func (DiscardLogger) Level() core.LogLevel { - return core.LOG_UNKNOWN +func (DiscardLogger) Level() LogLevel { + return LOG_UNKNOWN } // SetLevel empty implementation -func (DiscardLogger) SetLevel(l core.LogLevel) {} +func (DiscardLogger) SetLevel(l LogLevel) {} // ShowSQL empty implementation func (DiscardLogger) ShowSQL(show ...bool) {} @@ -64,17 +94,17 @@ func (DiscardLogger) IsShowSQL() bool { return false } -// SimpleLogger is the default implment of core.ILogger +// SimpleLogger is the default implment of ILogger type SimpleLogger struct { DEBUG *log.Logger ERR *log.Logger INFO *log.Logger WARN *log.Logger - level core.LogLevel + level LogLevel showSQL bool } -var _ core.ILogger = &SimpleLogger{} +var _ Logger = &SimpleLogger{} // NewSimpleLogger use a special io.Writer as logger output func NewSimpleLogger(out io.Writer) *SimpleLogger { @@ -87,7 +117,7 @@ func NewSimpleLogger2(out io.Writer, prefix string, flag int) *SimpleLogger { } // NewSimpleLogger3 let you customrize your logger prefix and flag and logLevel -func NewSimpleLogger3(out io.Writer, prefix string, flag int, l core.LogLevel) *SimpleLogger { +func NewSimpleLogger3(out io.Writer, prefix string, flag int, l LogLevel) *SimpleLogger { return &SimpleLogger{ DEBUG: log.New(out, fmt.Sprintf("%s [debug] ", prefix), flag), ERR: log.New(out, fmt.Sprintf("%s [error] ", prefix), flag), @@ -97,82 +127,82 @@ func NewSimpleLogger3(out io.Writer, prefix string, flag int, l core.LogLevel) * } } -// Error implement core.ILogger +// Error implement ILogger func (s *SimpleLogger) Error(v ...interface{}) { - if s.level <= core.LOG_ERR { + if s.level <= LOG_ERR { s.ERR.Output(2, fmt.Sprint(v...)) } return } -// Errorf implement core.ILogger +// Errorf implement ILogger func (s *SimpleLogger) Errorf(format string, v ...interface{}) { - if s.level <= core.LOG_ERR { + if s.level <= LOG_ERR { s.ERR.Output(2, fmt.Sprintf(format, v...)) } return } -// Debug implement core.ILogger +// Debug implement ILogger func (s *SimpleLogger) Debug(v ...interface{}) { - if s.level <= core.LOG_DEBUG { + if s.level <= LOG_DEBUG { s.DEBUG.Output(2, fmt.Sprint(v...)) } return } -// Debugf implement core.ILogger +// Debugf implement ILogger func (s *SimpleLogger) Debugf(format string, v ...interface{}) { - if s.level <= core.LOG_DEBUG { + if s.level <= LOG_DEBUG { s.DEBUG.Output(2, fmt.Sprintf(format, v...)) } return } -// Info implement core.ILogger +// Info implement ILogger func (s *SimpleLogger) Info(v ...interface{}) { - if s.level <= core.LOG_INFO { + if s.level <= LOG_INFO { s.INFO.Output(2, fmt.Sprint(v...)) } return } -// Infof implement core.ILogger +// Infof implement ILogger func (s *SimpleLogger) Infof(format string, v ...interface{}) { - if s.level <= core.LOG_INFO { + if s.level <= LOG_INFO { s.INFO.Output(2, fmt.Sprintf(format, v...)) } return } -// Warn implement core.ILogger +// Warn implement ILogger func (s *SimpleLogger) Warn(v ...interface{}) { - if s.level <= core.LOG_WARNING { + if s.level <= LOG_WARNING { s.WARN.Output(2, fmt.Sprint(v...)) } return } -// Warnf implement core.ILogger +// Warnf implement ILogger func (s *SimpleLogger) Warnf(format string, v ...interface{}) { - if s.level <= core.LOG_WARNING { + if s.level <= LOG_WARNING { s.WARN.Output(2, fmt.Sprintf(format, v...)) } return } -// Level implement core.ILogger -func (s *SimpleLogger) Level() core.LogLevel { +// Level implement ILogger +func (s *SimpleLogger) Level() LogLevel { return s.level } -// SetLevel implement core.ILogger -func (s *SimpleLogger) SetLevel(l core.LogLevel) { +// SetLevel implement ILogger +func (s *SimpleLogger) SetLevel(l LogLevel) { s.level = l return } -// ShowSQL implement core.ILogger +// ShowSQL implement ILogger func (s *SimpleLogger) ShowSQL(show ...bool) { if len(show) == 0 { s.showSQL = true @@ -181,7 +211,7 @@ func (s *SimpleLogger) ShowSQL(show ...bool) { s.showSQL = show[0] } -// IsShowSQL implement core.ILogger +// IsShowSQL implement ILogger func (s *SimpleLogger) IsShowSQL() bool { return s.showSQL } diff --git a/syslogger.go b/log/syslogger.go similarity index 88% rename from syslogger.go rename to log/syslogger.go index 11ba01e7..0b3e381c 100644 --- a/syslogger.go +++ b/log/syslogger.go @@ -4,16 +4,14 @@ // +build !windows,!nacl,!plan9 -package xorm +package log import ( "fmt" "log/syslog" - - "xorm.io/core" ) -var _ core.ILogger = &SyslogLogger{} +var _ Logger = &SyslogLogger{} // SyslogLogger will be depricated type SyslogLogger struct { @@ -21,7 +19,7 @@ type SyslogLogger struct { showSQL bool } -// NewSyslogLogger implements core.ILogger +// NewSyslogLogger implements Logger func NewSyslogLogger(w *syslog.Writer) *SyslogLogger { return &SyslogLogger{w: w} } @@ -67,12 +65,12 @@ func (s *SyslogLogger) Warnf(format string, v ...interface{}) { } // Level shows log level -func (s *SyslogLogger) Level() core.LogLevel { - return core.LOG_UNKNOWN +func (s *SyslogLogger) Level() LogLevel { + return LOG_UNKNOWN } // SetLevel always return error, as current log/syslog package doesn't allow to set priority level after syslog.Writer created -func (s *SyslogLogger) SetLevel(l core.LogLevel) {} +func (s *SyslogLogger) SetLevel(l LogLevel) {} // ShowSQL set if logging SQL func (s *SyslogLogger) ShowSQL(show ...bool) { diff --git a/migrate/migrate.go b/migrate/migrate.go index ed7b401c..82c58f90 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -13,7 +13,7 @@ type MigrateFunc func(*xorm.Engine) error // RollbackFunc is the func signature for rollbacking. type RollbackFunc func(*xorm.Engine) error -// InitSchemaFunc is the func signature for initializing the schema. +// InitSchemaFunc is the func signature for initializing the schemas. type InitSchemaFunc func(*xorm.Engine) error // Options define options for all migrations. @@ -34,7 +34,7 @@ type Migration struct { Rollback RollbackFunc } -// Migrate represents a collection of all migrations of a database schema. +// Migrate represents a collection of all migrations of a database schemas. type Migrate struct { db *xorm.Engine options *Options diff --git a/names/mapper.go b/names/mapper.go new file mode 100644 index 00000000..4aaf0844 --- /dev/null +++ b/names/mapper.go @@ -0,0 +1,258 @@ +// 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 names + +import ( + "strings" + "sync" +) + +// Mapper represents a name convertation between struct's fields name and table's column name +type Mapper interface { + Obj2Table(string) string + Table2Obj(string) string +} + +type CacheMapper struct { + oriMapper Mapper + obj2tableCache map[string]string + obj2tableMutex sync.RWMutex + table2objCache map[string]string + table2objMutex sync.RWMutex +} + +func NewCacheMapper(mapper Mapper) *CacheMapper { + return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), + table2objCache: make(map[string]string), + } +} + +func (m *CacheMapper) Obj2Table(o string) string { + m.obj2tableMutex.RLock() + t, ok := m.obj2tableCache[o] + m.obj2tableMutex.RUnlock() + if ok { + return t + } + + t = m.oriMapper.Obj2Table(o) + m.obj2tableMutex.Lock() + m.obj2tableCache[o] = t + m.obj2tableMutex.Unlock() + return t +} + +func (m *CacheMapper) Table2Obj(t string) string { + m.table2objMutex.RLock() + o, ok := m.table2objCache[t] + m.table2objMutex.RUnlock() + if ok { + return o + } + + o = m.oriMapper.Table2Obj(t) + m.table2objMutex.Lock() + m.table2objCache[t] = o + m.table2objMutex.Unlock() + return o +} + +// SameMapper implements IMapper and provides same name between struct and +// database table +type SameMapper struct { +} + +func (m SameMapper) Obj2Table(o string) string { + return o +} + +func (m SameMapper) Table2Obj(t string) string { + return t +} + +// SnakeMapper implements IMapper and provides name transaltion between +// struct and database table +type SnakeMapper struct { +} + +func snakeCasedName(name string) string { + newstr := make([]rune, 0) + for idx, chr := range name { + if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { + if idx > 0 { + newstr = append(newstr, '_') + } + chr -= ('A' - 'a') + } + newstr = append(newstr, chr) + } + + return string(newstr) +} + +func (mapper SnakeMapper) Obj2Table(name string) string { + return snakeCasedName(name) +} + +func titleCasedName(name string) string { + newstr := make([]rune, 0) + upNextChar := true + + name = strings.ToLower(name) + + for _, chr := range name { + switch { + case upNextChar: + upNextChar = false + if 'a' <= chr && chr <= 'z' { + chr -= ('a' - 'A') + } + case chr == '_': + upNextChar = true + continue + } + + newstr = append(newstr, chr) + } + + return string(newstr) +} + +func (mapper SnakeMapper) Table2Obj(name string) string { + return titleCasedName(name) +} + +// GonicMapper implements IMapper. It will consider initialisms when mapping names. +// E.g. id -> ID, user -> User and to table names: UserID -> user_id, MyUID -> my_uid +type GonicMapper map[string]bool + +func isASCIIUpper(r rune) bool { + return 'A' <= r && r <= 'Z' +} + +func toASCIIUpper(r rune) rune { + if 'a' <= r && r <= 'z' { + r -= ('a' - 'A') + } + return r +} + +func gonicCasedName(name string) string { + newstr := make([]rune, 0, len(name)+3) + for idx, chr := range name { + if isASCIIUpper(chr) && idx > 0 { + if !isASCIIUpper(newstr[len(newstr)-1]) { + newstr = append(newstr, '_') + } + } + + if !isASCIIUpper(chr) && idx > 1 { + l := len(newstr) + if isASCIIUpper(newstr[l-1]) && isASCIIUpper(newstr[l-2]) { + newstr = append(newstr, newstr[l-1]) + newstr[l-1] = '_' + } + } + + newstr = append(newstr, chr) + } + return strings.ToLower(string(newstr)) +} + +func (mapper GonicMapper) Obj2Table(name string) string { + return gonicCasedName(name) +} + +func (mapper GonicMapper) Table2Obj(name string) string { + newstr := make([]rune, 0) + + name = strings.ToLower(name) + parts := strings.Split(name, "_") + + for _, p := range parts { + _, isInitialism := mapper[strings.ToUpper(p)] + for i, r := range p { + if i == 0 || isInitialism { + r = toASCIIUpper(r) + } + newstr = append(newstr, r) + } + } + + return string(newstr) +} + +// LintGonicMapper is A GonicMapper that contains a list of common initialisms taken from golang/lint +var LintGonicMapper = GonicMapper{ + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SSH": true, + "TLS": true, + "TTL": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XSRF": true, + "XSS": true, +} + +// PrefixMapper provides prefix table name support +type PrefixMapper struct { + Mapper Mapper + Prefix string +} + +func (mapper PrefixMapper) Obj2Table(name string) string { + return mapper.Prefix + mapper.Mapper.Obj2Table(name) +} + +func (mapper PrefixMapper) Table2Obj(name string) string { + return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) +} + +func NewPrefixMapper(mapper Mapper, prefix string) PrefixMapper { + return PrefixMapper{mapper, prefix} +} + +// SuffixMapper provides suffix table name support +type SuffixMapper struct { + Mapper Mapper + Suffix string +} + +func (mapper SuffixMapper) Obj2Table(name string) string { + return mapper.Mapper.Obj2Table(name) + mapper.Suffix +} + +func (mapper SuffixMapper) Table2Obj(name string) string { + return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) +} + +func NewSuffixMapper(mapper Mapper, suffix string) SuffixMapper { + return SuffixMapper{mapper, suffix} +} diff --git a/names/mapper_test.go b/names/mapper_test.go new file mode 100644 index 00000000..0edfd2a8 --- /dev/null +++ b/names/mapper_test.go @@ -0,0 +1,49 @@ +// 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 names + +import ( + "testing" +) + +func TestGonicMapperFromObj(t *testing.T) { + testCases := map[string]string{ + "HTTPLib": "http_lib", + "id": "id", + "ID": "id", + "IDa": "i_da", + "iDa": "i_da", + "IDAa": "id_aa", + "aID": "a_id", + "aaID": "aa_id", + "aaaID": "aaa_id", + "MyREalFunkYLONgNAME": "my_r_eal_funk_ylo_ng_name", + } + + for in, expected := range testCases { + out := gonicCasedName(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} + +func TestGonicMapperToObj(t *testing.T) { + testCases := map[string]string{ + "http_lib": "HTTPLib", + "id": "ID", + "ida": "Ida", + "id_aa": "IDAa", + "aa_id": "AaID", + "my_r_eal_funk_ylo_ng_name": "MyREalFunkYloNgName", + } + + for in, expected := range testCases { + out := LintGonicMapper.Table2Obj(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} diff --git a/table_name.go b/names/table_name.go similarity index 70% rename from table_name.go rename to names/table_name.go index 632c2879..6dd4e552 100644 --- a/table_name.go +++ b/names/table_name.go @@ -2,15 +2,22 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package names import ( "reflect" - - "xorm.io/core" ) -func getTableName(mapper core.IMapper, v reflect.Value) string { +// TableName table name interface to define customerize table name +type TableName interface { + TableName() string +} + +var ( + tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() +) + +func GetTableName(mapper Mapper, v reflect.Value) string { if t, ok := v.Interface().(TableName); ok { return t.TableName() } diff --git a/table_name_test.go b/names/table_name_test.go similarity index 55% rename from table_name_test.go rename to names/table_name_test.go index 6cb0ceaa..1f20bfaa 100644 --- a/table_name_test.go +++ b/names/table_name_test.go @@ -2,17 +2,45 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package names import ( "reflect" "testing" + "time" "github.com/stretchr/testify/assert" - - "xorm.io/core" ) +type Userinfo struct { + Uid int64 `xorm:"id pk not null autoincr"` + Username string `xorm:"unique"` + Departname string + Alias string `xorm:"-"` + Created time.Time + Detail Userdetail `xorm:"detail_id int(11)"` + Height float64 + Avatar []byte + IsMan bool +} + +type Userdetail struct { + Id int64 + Intro string `xorm:"text"` + Profile string `xorm:"varchar(2000)"` +} + +type MyGetCustomTableImpletation struct { + Id int64 `json:"id"` + Name string `json:"name"` +} + +const getCustomTableName = "GetCustomTableInterface" + +func (MyGetCustomTableImpletation) TableName() string { + return getCustomTableName +} + type TestTableNameStruct struct{} func (t *TestTableNameStruct) TableName() string { @@ -21,53 +49,53 @@ func (t *TestTableNameStruct) TableName() string { func TestGetTableName(t *testing.T) { var kases = []struct { - mapper core.IMapper + mapper Mapper v reflect.Value expectedTableName string }{ { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(new(Userinfo)), "userinfo", }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(Userinfo{}), "userinfo", }, { - core.SameMapper{}, + SameMapper{}, reflect.ValueOf(new(Userinfo)), "Userinfo", }, { - core.SameMapper{}, + SameMapper{}, reflect.ValueOf(Userinfo{}), "Userinfo", }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(new(MyGetCustomTableImpletation)), getCustomTableName, }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(MyGetCustomTableImpletation{}), getCustomTableName, }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(MyGetCustomTableImpletation{}), getCustomTableName, }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(new(TestTableNameStruct)), new(TestTableNameStruct).TableName(), }, } for _, kase := range kases { - assert.EqualValues(t, kase.expectedTableName, getTableName(kase.mapper, kase.v)) + assert.EqualValues(t, kase.expectedTableName, GetTableName(kase.mapper, kase.v)) } } diff --git a/rows.go b/rows.go index bdd44589..b52b889d 100644 --- a/rows.go +++ b/rows.go @@ -9,7 +9,7 @@ import ( "fmt" "reflect" - "xorm.io/core" + "xorm.io/xorm/core" ) // Rows rows wrapper a rows to diff --git a/schemas/column.go b/schemas/column.go new file mode 100644 index 00000000..9466f6a5 --- /dev/null +++ b/schemas/column.go @@ -0,0 +1,117 @@ +// 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 schemas + +import ( + "fmt" + "reflect" + "strings" + "time" +) + +const ( + TWOSIDES = iota + 1 + ONLYTODB + ONLYFROMDB +) + +// Column defines database column +type Column struct { + Name string + TableName string + FieldName string + SQLType SQLType + IsJSON bool + Length int + Length2 int + Nullable bool + Default string + Indexes map[string]int + IsPrimaryKey bool + IsAutoIncrement bool + MapType int + IsCreated bool + IsUpdated bool + IsDeleted bool + IsCascade bool + IsVersion bool + DefaultIsEmpty bool // false means column has no default set, but not default value is empty + EnumOptions map[string]int + SetOptions map[string]int + DisableTimeZone bool + TimeZone *time.Location // column specified time zone + Comment string +} + +// NewColumn creates a new column +func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { + return &Column{ + Name: name, + TableName: "", + FieldName: fieldName, + SQLType: sqlType, + Length: len1, + Length2: len2, + Nullable: nullable, + Default: "", + Indexes: make(map[string]int), + IsPrimaryKey: false, + IsAutoIncrement: false, + MapType: TWOSIDES, + IsCreated: false, + IsUpdated: false, + IsDeleted: false, + IsCascade: false, + IsVersion: false, + DefaultIsEmpty: true, // default should be no default + EnumOptions: make(map[string]int), + Comment: "", + } +} + +// ValueOf returns column's filed of struct's value +func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { + dataStruct := reflect.Indirect(reflect.ValueOf(bean)) + return col.ValueOfV(&dataStruct) +} + +// 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())) + } + fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) + } else { + return nil, fmt.Errorf("field %v is not valid", col.FieldName) + } + } + + if !fieldValue.IsValid() { + return nil, fmt.Errorf("field %v is not valid", col.FieldName) + } + + return &fieldValue, nil +} diff --git a/schemas/index.go b/schemas/index.go new file mode 100644 index 00000000..e5738c93 --- /dev/null +++ b/schemas/index.go @@ -0,0 +1,72 @@ +// 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 schemas + +import ( + "fmt" + "strings" +) + +// enumerate all index types +const ( + IndexType = iota + 1 + UniqueType +) + +// Index represents a database index +type Index struct { + IsRegular bool + Name string + Type int + Cols []string +} + +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), ".") + tableName = tableParts[len(tableParts)-1] + if index.Type == UniqueType { + return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) + } + return fmt.Sprintf("IDX_%v_%v", tableName, index.Name) + } + return index.Name +} + +// AddColumn add columns which will be composite index +func (index *Index) AddColumn(cols ...string) { + for _, col := range cols { + index.Cols = append(index.Cols, col) + } +} + +func (index *Index) Equal(dst *Index) bool { + if index.Type != dst.Type { + return false + } + if len(index.Cols) != len(dst.Cols) { + return false + } + + for i := 0; i < len(index.Cols); i++ { + var found bool + for j := 0; j < len(dst.Cols); j++ { + if index.Cols[i] == dst.Cols[j] { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +// NewIndex new an index object +func NewIndex(name string, indexType int) *Index { + return &Index{true, name, indexType, make([]string, 0)} +} diff --git a/schemas/pk.go b/schemas/pk.go new file mode 100644 index 00000000..3fd3d28b --- /dev/null +++ b/schemas/pk.go @@ -0,0 +1,30 @@ +// 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 schemas + +import ( + "bytes" + "encoding/gob" +) + +type PK []interface{} + +func NewPK(pks ...interface{}) *PK { + p := PK(pks) + return &p +} + +func (p *PK) ToString() (string, error) { + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(*p) + return buf.String(), err +} + +func (p *PK) FromString(content string) error { + dec := gob.NewDecoder(bytes.NewBufferString(content)) + err := dec.Decode(p) + return err +} diff --git a/schemas/pk_test.go b/schemas/pk_test.go new file mode 100644 index 00000000..a88b70da --- /dev/null +++ b/schemas/pk_test.go @@ -0,0 +1,36 @@ +// 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 schemas + +import ( + "reflect" + "testing" +) + +func TestPK(t *testing.T) { + p := NewPK(1, 3, "string") + str, err := p.ToString() + if err != nil { + t.Error(err) + } + t.Log(str) + + s := &PK{} + err = s.FromString(str) + if err != nil { + t.Error(err) + } + t.Log(s) + + if len(*p) != len(*s) { + t.Fatal("p", *p, "should be equal", *s) + } + + for i, ori := range *p { + if ori != (*s)[i] { + t.Fatal("ori", ori, reflect.ValueOf(ori), "should be equal", (*s)[i], reflect.ValueOf((*s)[i])) + } + } +} diff --git a/schemas/table.go b/schemas/table.go new file mode 100644 index 00000000..b32a276a --- /dev/null +++ b/schemas/table.go @@ -0,0 +1,156 @@ +// 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 schemas + +import ( + "reflect" + "strings" + //"xorm.io/xorm/cache" +) + +// Table represents a database table +type Table struct { + Name string + Type reflect.Type + columnsSeq []string + columnsMap map[string][]*Column + columns []*Column + Indexes map[string]*Index + PrimaryKeys []string + AutoIncrement string + Created map[string]bool + Updated string + Deleted string + Version string + //Cacher caches.Cacher + StoreEngine string + Charset string + Comment string +} + +func (table *Table) Columns() []*Column { + return table.columns +} + +func (table *Table) ColumnsSeq() []string { + return table.columnsSeq +} + +func NewEmptyTable() *Table { + return NewTable("", nil) +} + +// NewTable creates a new Table object +func NewTable(name string, t reflect.Type) *Table { + return &Table{Name: name, Type: t, + columnsSeq: make([]string, 0), + columns: make([]*Column, 0), + columnsMap: make(map[string][]*Column), + Indexes: make(map[string]*Index), + Created: make(map[string]bool), + PrimaryKeys: make([]string, 0), + } +} + +func (table *Table) columnsByName(name string) []*Column { + n := len(name) + + for k := range table.columnsMap { + if len(k) != n { + continue + } + if strings.EqualFold(k, name) { + return table.columnsMap[k] + } + } + return nil +} + +func (table *Table) GetColumn(name string) *Column { + + cols := table.columnsByName(name) + + if cols != nil { + return cols[0] + } + + return nil +} + +func (table *Table) GetColumnIdx(name string, idx int) *Column { + cols := table.columnsByName(name) + + if cols != nil && idx < len(cols) { + return cols[idx] + } + + return nil +} + +// PKColumns reprents all primary key columns +func (table *Table) PKColumns() []*Column { + columns := make([]*Column, len(table.PrimaryKeys)) + for i, name := range table.PrimaryKeys { + columns[i] = table.GetColumn(name) + } + return columns +} + +func (table *Table) ColumnType(name string) reflect.Type { + t, _ := table.Type.FieldByName(name) + return t.Type +} + +func (table *Table) AutoIncrColumn() *Column { + return table.GetColumn(table.AutoIncrement) +} + +func (table *Table) VersionColumn() *Column { + return table.GetColumn(table.Version) +} + +func (table *Table) UpdatedColumn() *Column { + return table.GetColumn(table.Updated) +} + +func (table *Table) DeletedColumn() *Column { + return table.GetColumn(table.Deleted) +} + +// AddColumn adds a column to table +func (table *Table) AddColumn(col *Column) { + table.columnsSeq = append(table.columnsSeq, col.Name) + table.columns = append(table.columns, col) + colName := strings.ToLower(col.Name) + if c, ok := table.columnsMap[colName]; ok { + table.columnsMap[colName] = append(c, col) + } else { + table.columnsMap[colName] = []*Column{col} + } + + if col.IsPrimaryKey { + table.PrimaryKeys = append(table.PrimaryKeys, col.Name) + } + if col.IsAutoIncrement { + table.AutoIncrement = col.Name + } + if col.IsCreated { + table.Created[col.Name] = true + } + if col.IsUpdated { + table.Updated = col.Name + } + if col.IsDeleted { + table.Deleted = col.Name + } + if col.IsVersion { + table.Version = col.Name + } +} + +// AddIndex adds an index or an unique to table +func (table *Table) AddIndex(index *Index) { + table.Indexes[index.Name] = index +} diff --git a/schemas/table_test.go b/schemas/table_test.go new file mode 100644 index 00000000..9bf10e33 --- /dev/null +++ b/schemas/table_test.go @@ -0,0 +1,111 @@ +// 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 schemas + +import ( + "strings" + "testing" +) + +var testsGetColumn = []struct { + name string + idx int +}{ + {"Id", 0}, + {"Deleted", 0}, + {"Caption", 0}, + {"Code_1", 0}, + {"Code_2", 0}, + {"Code_3", 0}, + {"Parent_Id", 0}, + {"Latitude", 0}, + {"Longitude", 0}, +} + +var table *Table + +func init() { + + table = NewEmptyTable() + + var name string + + for i := 0; i < len(testsGetColumn); i++ { + // as in Table.AddColumn func + name = strings.ToLower(testsGetColumn[i].name) + + table.columnsMap[name] = append(table.columnsMap[name], &Column{}) + } +} + +func TestGetColumn(t *testing.T) { + + for _, test := range testsGetColumn { + if table.GetColumn(test.name) == nil { + t.Error("Column not found!") + } + } +} + +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) + } + } +} + +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) + } + } + } +} + +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 + } else { + b.Errorf("Bad idx in: %s, %d", test.name, test.idx) + } + } else { + b.Errorf("Column not found: %s, %d", test.name, test.idx) + } + } + } +} + +func BenchmarkGetColumn(b *testing.B) { + + for i := 0; i < b.N; i++ { + for _, test := range testsGetColumn { + if table.GetColumn(test.name) == nil { + b.Errorf("Column not found:%s", test.name) + } + } + } +} + +func BenchmarkGetColumnIdx(b *testing.B) { + + for i := 0; i < b.N; i++ { + for _, test := range testsGetColumn { + if table.GetColumnIdx(test.name, test.idx) == nil { + b.Errorf("Column not found:%s, %d", test.name, test.idx) + } + } + } +} diff --git a/schemas/type.go b/schemas/type.go new file mode 100644 index 00000000..2aaa2a44 --- /dev/null +++ b/schemas/type.go @@ -0,0 +1,325 @@ +// 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 schemas + +import ( + "reflect" + "sort" + "strings" + "time" +) + +const ( + POSTGRES = "postgres" + SQLITE = "sqlite3" + MYSQL = "mysql" + MSSQL = "mssql" + ORACLE = "oracle" +) + +// SQLType represents SQL types +type SQLType struct { + Name string + DefaultLength int + DefaultLength2 int +} + +const ( + UNKNOW_TYPE = iota + TEXT_TYPE + BLOB_TYPE + TIME_TYPE + NUMERIC_TYPE +) + +func (s *SQLType) IsType(st int) bool { + if t, ok := SqlTypes[s.Name]; ok && t == st { + return true + } + return false +} + +func (s *SQLType) IsText() bool { + return s.IsType(TEXT_TYPE) +} + +func (s *SQLType) IsBlob() bool { + return s.IsType(BLOB_TYPE) +} + +func (s *SQLType) IsTime() bool { + return s.IsType(TIME_TYPE) +} + +func (s *SQLType) IsNumeric() bool { + return s.IsType(NUMERIC_TYPE) +} + +func (s *SQLType) IsJson() bool { + return s.Name == Json || s.Name == Jsonb +} + +var ( + Bit = "BIT" + TinyInt = "TINYINT" + SmallInt = "SMALLINT" + MediumInt = "MEDIUMINT" + Int = "INT" + Integer = "INTEGER" + BigInt = "BIGINT" + + Enum = "ENUM" + Set = "SET" + + Char = "CHAR" + Varchar = "VARCHAR" + NChar = "NCHAR" + NVarchar = "NVARCHAR" + TinyText = "TINYTEXT" + Text = "TEXT" + NText = "NTEXT" + Clob = "CLOB" + MediumText = "MEDIUMTEXT" + LongText = "LONGTEXT" + Uuid = "UUID" + UniqueIdentifier = "UNIQUEIDENTIFIER" + SysName = "SYSNAME" + + Date = "DATE" + DateTime = "DATETIME" + SmallDateTime = "SMALLDATETIME" + Time = "TIME" + TimeStamp = "TIMESTAMP" + TimeStampz = "TIMESTAMPZ" + Year = "YEAR" + + Decimal = "DECIMAL" + Numeric = "NUMERIC" + Money = "MONEY" + SmallMoney = "SMALLMONEY" + + Real = "REAL" + Float = "FLOAT" + Double = "DOUBLE" + + Binary = "BINARY" + VarBinary = "VARBINARY" + TinyBlob = "TINYBLOB" + Blob = "BLOB" + MediumBlob = "MEDIUMBLOB" + LongBlob = "LONGBLOB" + Bytea = "BYTEA" + + Bool = "BOOL" + Boolean = "BOOLEAN" + + Serial = "SERIAL" + BigSerial = "BIGSERIAL" + + Json = "JSON" + Jsonb = "JSONB" + + 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, + + Enum: TEXT_TYPE, + Set: TEXT_TYPE, + Json: TEXT_TYPE, + Jsonb: TEXT_TYPE, + + Char: TEXT_TYPE, + NChar: TEXT_TYPE, + Varchar: TEXT_TYPE, + NVarchar: TEXT_TYPE, + TinyText: TEXT_TYPE, + Text: TEXT_TYPE, + NText: TEXT_TYPE, + MediumText: TEXT_TYPE, + LongText: TEXT_TYPE, + Uuid: TEXT_TYPE, + Clob: TEXT_TYPE, + SysName: TEXT_TYPE, + + Date: TIME_TYPE, + DateTime: TIME_TYPE, + Time: TIME_TYPE, + TimeStamp: TIME_TYPE, + TimeStampz: TIME_TYPE, + SmallDateTime: TIME_TYPE, + Year: TIME_TYPE, + + Decimal: NUMERIC_TYPE, + Numeric: NUMERIC_TYPE, + Real: NUMERIC_TYPE, + Float: NUMERIC_TYPE, + Double: NUMERIC_TYPE, + Money: NUMERIC_TYPE, + SmallMoney: NUMERIC_TYPE, + + Binary: BLOB_TYPE, + VarBinary: BLOB_TYPE, + + TinyBlob: BLOB_TYPE, + Blob: BLOB_TYPE, + MediumBlob: BLOB_TYPE, + LongBlob: BLOB_TYPE, + Bytea: BLOB_TYPE, + UniqueIdentifier: BLOB_TYPE, + + Bool: NUMERIC_TYPE, + + Serial: NUMERIC_TYPE, + BigSerial: NUMERIC_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 ( + 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 +) + +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) + + 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) + + Float32Type = reflect.TypeOf(c_FLOAT32_DEFAULT) + Float64Type = reflect.TypeOf(c_FLOAT64_DEFAULT) + + Complex64Type = reflect.TypeOf(c_COMPLEX64_DEFAULT) + Complex128Type = reflect.TypeOf(c_COMPLEX128_DEFAULT) + + StringType = reflect.TypeOf(c_EMPTY_STRING) + BoolType = reflect.TypeOf(c_BOOL_DEFAULT) + ByteType = reflect.TypeOf(c_BYTE_DEFAULT) + BytesType = reflect.SliceOf(ByteType) + + TimeType = reflect.TypeOf(c_TIME_DEFAULT) +) + +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) +) + +// 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: + st = SQLType{Int, 0, 0} + case reflect.Int64, reflect.Uint64: + st = SQLType{BigInt, 0, 0} + case reflect.Float32: + st = SQLType{Float, 0, 0} + case reflect.Float64: + st = SQLType{Double, 0, 0} + 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) { + st = SQLType{Blob, 0, 0} + } else { + st = SQLType{Text, 0, 0} + } + case reflect.Bool: + st = SQLType{Bool, 0, 0} + case reflect.String: + st = SQLType{Varchar, 255, 0} + case reflect.Struct: + if t.ConvertibleTo(TimeType) { + st = SQLType{DateTime, 0, 0} + } else { + // TODO need to handle association struct + st = SQLType{Text, 0, 0} + } + case reflect.Ptr: + st = Type2SQLType(t.Elem()) + default: + st = SQLType{Text, 0, 0} + } + return +} + +// default sql type change to go types +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) + case BigInt, BigSerial: + return reflect.TypeOf(int64(1)) + case Float, Real: + return reflect.TypeOf(float32(1)) + case Double: + return reflect.TypeOf(float64(1)) + case Char, NChar, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName: + return reflect.TypeOf("") + case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier: + return reflect.TypeOf([]byte{}) + case Bool: + return reflect.TypeOf(true) + case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year: + return reflect.TypeOf(c_TIME_DEFAULT) + case Decimal, Numeric, Money, SmallMoney: + return reflect.TypeOf("") + default: + return reflect.TypeOf("") + } +} diff --git a/session.go b/session.go index 83071935..57e4055b 100644 --- a/session.go +++ b/session.go @@ -14,7 +14,8 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) type sessionType int @@ -306,8 +307,8 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, return } -func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) (*reflect.Value, error) { - var col *core.Column +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} } @@ -328,8 +329,8 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *c type Cell *interface{} func (session *Session) rows2Beans(rows *core.Rows, fields []string, - table *core.Table, newElemFunc func([]string) reflect.Value, - sliceValueSetFunc func(*reflect.Value, core.PK) error) error { + table *schemas.Table, newElemFunc func([]string) reflect.Value, + sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { for rows.Next() { var newValue = newElemFunc(fields) bean := newValue.Interface() @@ -377,7 +378,7 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa return scanResults, nil } -func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) { +func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { defer func() { if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { for ii, key := range fields { @@ -421,7 +422,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } var tempMap = make(map[string]int) - var pk core.PK + var pk schemas.PK for ii, key := range fields { var idx int var ok bool @@ -451,7 +452,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { if data, err := value2Bytes(&rawValue); err == nil { if err := structConvert.FromDB(data); err != nil { return nil, err @@ -463,12 +464,12 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } } - if _, ok := fieldValue.Interface().(core.Conversion); ok { + if _, ok := fieldValue.Interface().(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().(core.Conversion).FromDB(data) + fieldValue.Interface().(Conversion).FromDB(data) } else { return nil, err } @@ -488,7 +489,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b var bs []byte if rawValueType.Kind() == reflect.String { bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(core.BytesType) { + } else if rawValueType.ConvertibleTo(schemas.BytesType) { bs = vv.Bytes() } else { return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) @@ -525,7 +526,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b var bs []byte if rawValueType.Kind() == reflect.String { bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(core.BytesType) { + } else if rawValueType.ConvertibleTo(schemas.BytesType) { bs = vv.Bytes() } @@ -607,16 +608,16 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b fieldValue.SetUint(uint64(vv.Int())) } case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { + if fieldType.ConvertibleTo(schemas.TimeType) { dbTZ := session.engine.DatabaseTZ if col.TimeZone != nil { dbTZ = col.TimeZone } - if rawValueType == core.TimeType { + if rawValueType == schemas.TimeType { hasAssigned = true - t := vv.Convert(core.TimeType).Interface().(time.Time) + 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 @@ -628,8 +629,8 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b t = t.In(session.engine.TZLocation) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } else if rawValueType == core.IntType || rawValueType == core.Int64Type || - rawValueType == core.Int32Type { + } else if rawValueType == schemas.IntType || rawValueType == schemas.Int64Type || + rawValueType == schemas.Int32Type { hasAssigned = true t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation) @@ -696,7 +697,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b if len(table.PrimaryKeys) != 1 { return nil, errors.New("unsupported non or composited primary key cascade") } - var pk = make(core.PK, len(table.PrimaryKeys)) + var pk = make(schemas.PK, len(table.PrimaryKeys)) pk[0], err = asKind(vv, rawValueType) if err != nil { return nil, err @@ -722,97 +723,97 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b // !nashtsai! TODO merge duplicated codes above switch fieldType { // following types case matching ptr's native type, therefore assign ptr directly - case core.PtrStringType: + case schemas.PtrStringType: if rawValueType.Kind() == reflect.String { x := vv.String() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrBoolType: + case schemas.PtrBoolType: if rawValueType.Kind() == reflect.Bool { x := vv.Bool() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrTimeType: - if rawValueType == core.PtrTimeType { + case schemas.PtrTimeType: + if rawValueType == schemas.PtrTimeType { hasAssigned = true var x = rawValue.Interface().(time.Time) fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrFloat64Type: + case schemas.PtrFloat64Type: if rawValueType.Kind() == reflect.Float64 { x := vv.Float() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrUint64Type: + case schemas.PtrUint64Type: if rawValueType.Kind() == reflect.Int64 { var x = uint64(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt64Type: + case schemas.PtrInt64Type: if rawValueType.Kind() == reflect.Int64 { x := vv.Int() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrFloat32Type: + case schemas.PtrFloat32Type: if rawValueType.Kind() == reflect.Float64 { var x = float32(vv.Float()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrIntType: + case schemas.PtrIntType: if rawValueType.Kind() == reflect.Int64 { var x = int(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt32Type: + case schemas.PtrInt32Type: if rawValueType.Kind() == reflect.Int64 { var x = int32(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt8Type: + case schemas.PtrInt8Type: if rawValueType.Kind() == reflect.Int64 { var x = int8(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt16Type: + case schemas.PtrInt16Type: if rawValueType.Kind() == reflect.Int64 { var x = int16(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrUintType: + case schemas.PtrUintType: if rawValueType.Kind() == reflect.Int64 { var x = uint(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrUint32Type: + case schemas.PtrUint32Type: if rawValueType.Kind() == reflect.Int64 { var x = uint32(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.Uint8Type: + case schemas.Uint8Type: if rawValueType.Kind() == reflect.Int64 { var x = uint8(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.Uint16Type: + case schemas.Uint16Type: if rawValueType.Kind() == reflect.Int64 { var x = uint16(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.Complex64Type: + case schemas.Complex64Type: var x complex64 if len([]byte(vv.String())) > 0 { err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) @@ -822,7 +823,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b fieldValue.Set(reflect.ValueOf(&x)) } hasAssigned = true - case core.Complex128Type: + case schemas.Complex128Type: var x complex128 if len([]byte(vv.String())) > 0 { err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) diff --git a/session_cols.go b/session_cols.go index 1558074f..4f7dc6cf 100644 --- a/session_cols.go +++ b/session_cols.go @@ -9,10 +9,10 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/schemas" ) -func setColumnInt(bean interface{}, col *core.Column, t int64) { +func setColumnInt(bean interface{}, col *schemas.Column, t int64) { v, err := col.ValueOf(bean) if err != nil { return @@ -27,7 +27,7 @@ func setColumnInt(bean interface{}, col *core.Column, t int64) { } } -func setColumnTime(bean interface{}, col *core.Column, t time.Time) { +func setColumnTime(bean interface{}, col *schemas.Column, t time.Time) { v, err := col.ValueOf(bean) if err != nil { return @@ -44,7 +44,7 @@ func setColumnTime(bean interface{}, col *core.Column, t time.Time) { } } -func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) { +func getFlagForColumn(m map[string]bool, col *schemas.Column) (val bool, has bool) { if len(m) == 0 { return false, false } diff --git a/session_cols_test.go b/session_cols_test.go index 96cb1620..58b4e841 100644 --- a/session_cols_test.go +++ b/session_cols_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestSetExpr(t *testing.T) { @@ -45,7 +45,7 @@ func TestSetExpr(t *testing.T) { assert.EqualValues(t, 1, cnt) var not = "NOT" - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { not = "~" } cnt, err = testEngine.SetExpr("show", not+" `show`").ID(1).Update(new(UserExpr)) diff --git a/session_convert.go b/session_convert.go index 7f11354d..24c51011 100644 --- a/session_convert.go +++ b/session_convert.go @@ -14,10 +14,10 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/schemas" ) -func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) { +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 @@ -54,14 +54,14 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti } 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 == core.Time { + } 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.DBType() == core.MYSQL && len(sdata) > 8 { + if session.engine.dialect.DBType() == schemas.MYSQL && len(sdata) > 8 { sdata = sdata[len(sdata)-8:] } @@ -80,7 +80,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti return } -func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) { +func (session *Session) byte2Time(col *schemas.Column, data []byte) (outTime time.Time, outErr error) { return session.str2Time(col, string(data)) } @@ -89,12 +89,12 @@ var ( ) // convert a db data([]byte) to a field value -func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { +func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Value, data []byte) error { + if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { return structConvert.FromDB(data) } - if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Interface().(Conversion); ok { return structConvert.FromDB(data) } @@ -157,8 +157,8 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, var x int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && - session.engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API + if col.SQLType.Name == schemas.Bit && + session.engine.dialect.DBType() == schemas.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API if len(data) == 1 { x = int64(data[0]) } else { @@ -199,7 +199,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error()) } } else { - if fieldType.ConvertibleTo(core.TimeType) { + if fieldType.ConvertibleTo(schemas.TimeType) { x, err := session.byte2Time(col, data) if err != nil { return err @@ -217,7 +217,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return errors.New("unsupported composited primary key cascade") } - var pk = make(core.PK, len(table.PrimaryKeys)) + 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 { @@ -247,11 +247,11 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, //typeStr := fieldType.String() switch fieldType.Elem().Kind() { // case "*string": - case core.StringType.Kind(): + case schemas.StringType.Kind(): x := string(data) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*bool": - case core.BoolType.Kind(): + case schemas.BoolType.Kind(): d := string(data) v, err := strconv.ParseBool(d) if err != nil { @@ -259,7 +259,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType)) // case "*complex64": - case core.Complex64Type.Kind(): + case schemas.Complex64Type.Kind(): var x complex64 if len(data) > 0 { err := DefaultJSONHandler.Unmarshal(data, &x) @@ -270,7 +270,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) } // case "*complex128": - case core.Complex128Type.Kind(): + case schemas.Complex128Type.Kind(): var x complex128 if len(data) > 0 { err := DefaultJSONHandler.Unmarshal(data, &x) @@ -281,14 +281,14 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) } // case "*float64": - case core.Float64Type.Kind(): + 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 core.Float32Type.Kind(): + case schemas.Float32Type.Kind(): var x float32 x1, err := strconv.ParseFloat(string(data), 32) if err != nil { @@ -297,7 +297,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = float32(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint64": - case core.Uint64Type.Kind(): + case schemas.Uint64Type.Kind(): var x uint64 x, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -305,7 +305,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint": - case core.UintType.Kind(): + case schemas.UintType.Kind(): var x uint x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -314,7 +314,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint32": - case core.Uint32Type.Kind(): + case schemas.Uint32Type.Kind(): var x uint32 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -323,7 +323,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint32(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint8": - case core.Uint8Type.Kind(): + case schemas.Uint8Type.Kind(): var x uint8 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -332,7 +332,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint8(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint16": - case core.Uint16Type.Kind(): + case schemas.Uint16Type.Kind(): var x uint16 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -341,12 +341,12 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint16(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int64": - case core.Int64Type.Kind(): + 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 == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int64(data[0]) @@ -365,13 +365,13 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int": - case core.IntType.Kind(): + 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 == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int(data[0]) @@ -393,14 +393,14 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int32": - case core.Int32Type.Kind(): + 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 == core.Bit && - session.engine.dialect.DBType() == core.MYSQL { + if col.SQLType.Name == schemas.Bit && + session.engine.dialect.DBType() == schemas.MYSQL { if len(data) == 1 { x = int32(data[0]) } else { @@ -421,13 +421,13 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int8": - case core.Int8Type.Kind(): + 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 == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int8(data[0]) @@ -449,13 +449,13 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int16": - case core.Int16Type.Kind(): + 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 == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int16(data[0]) @@ -480,7 +480,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, case reflect.Struct: switch fieldType { // case "*.time.Time": - case core.PtrTimeType: + case schemas.PtrTimeType: x, err := session.byte2Time(col, data) if err != nil { return err @@ -499,7 +499,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return errors.New("unsupported composited primary key cascade") } - var pk = make(core.PK, len(table.PrimaryKeys)) + 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 { @@ -536,9 +536,9 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } // convert a field value of a struct to interface for put into db -func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) { +func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect.Value) (interface{}, error) { if fieldValue.CanAddr() { - if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if fieldConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err @@ -550,7 +550,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val } } - if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok { + if fieldConvert, ok := fieldValue.Interface().(Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err @@ -583,8 +583,8 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val case reflect.String: return fieldValue.String(), nil case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) tf := session.engine.formatColTime(col, t) return tf, nil } else if fieldType.ConvertibleTo(nullFloatType) { diff --git a/session_delete.go b/session_delete.go index 7b0a0641..2afe068d 100644 --- a/session_delete.go +++ b/session_delete.go @@ -9,10 +9,11 @@ import ( "fmt" "strconv" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) -func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, args ...interface{}) error { +func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { return ErrCacheFailed @@ -29,17 +30,17 @@ func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, cacher := session.engine.getCacher(tableName) pkColumns := table.PKColumns() - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { resultsSlice, err := session.queryBytes(newsql, args...) if err != nil { return err } - ids = make([]core.PK, 0) + ids = make([]schemas.PK, 0) if len(resultsSlice) > 0 { for _, data := range resultsSlice { var id int64 - var pk core.PK = make([]interface{}, 0) + var pk schemas.PK = make([]interface{}, 0) for _, col := range pkColumns { if v, ok := data[col.Name]; !ok { return errors.New("no id") @@ -127,14 +128,14 @@ func (session *Session) Delete(bean interface{}) (int64, error) { if len(orderSQL) > 0 { switch session.engine.dialect.DBType() { - case core.POSTGRES: + case schemas.POSTGRES: inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { deleteSQL += " AND " + inSQL } else { deleteSQL += " WHERE " + inSQL } - case core.SQLITE: + case schemas.SQLITE: inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { deleteSQL += " AND " + inSQL @@ -142,7 +143,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { deleteSQL += " WHERE " + inSQL } // TODO: how to handle delete limit on mssql? - case core.MSSQL: + case schemas.MSSQL: return 0, ErrNotImplemented default: deleteSQL += orderSQL @@ -156,7 +157,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { copy(argsForCache, condArgs) argsForCache = append(condArgs, argsForCache...) } else { - // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for cache. + // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for caches. copy(argsForCache, condArgs) argsForCache = append(condArgs, argsForCache...) @@ -168,14 +169,14 @@ func (session *Session) Delete(bean interface{}) (int64, error) { if len(orderSQL) > 0 { switch session.engine.dialect.DBType() { - case core.POSTGRES: + 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 core.SQLITE: + case schemas.SQLITE: inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { realSQL += " AND " + inSQL @@ -183,7 +184,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { realSQL += " WHERE " + inSQL } // TODO: how to handle delete limit on mssql? - case core.MSSQL: + case schemas.MSSQL: return 0, ErrNotImplemented default: realSQL += orderSQL diff --git a/session_delete_test.go b/session_delete_test.go index ca0402fd..3d0fa1a8 100644 --- a/session_delete_test.go +++ b/session_delete_test.go @@ -9,7 +9,8 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) func TestDelete(t *testing.T) { @@ -26,7 +27,7 @@ func TestDelete(t *testing.T) { defer session.Close() var err error - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT userinfo_delete ON") @@ -38,7 +39,7 @@ func TestDelete(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -159,7 +160,7 @@ func TestCacheDelete(t *testing.T) { assert.NoError(t, prepareEngine()) oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher(NewMemoryStore(), 1000) + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 1000) testEngine.SetDefaultCacher(cacher) type CacheDeleteStruct struct { diff --git a/session_exist.go b/session_exist.go index bce2758d..153bb219 100644 --- a/session_exist.go +++ b/session_exist.go @@ -10,7 +10,7 @@ import ( "reflect" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // Exist returns true if the record exist otherwise return false @@ -45,18 +45,18 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { return false, err } - if session.engine.dialect.DBType() == core.MSSQL { + if session.engine.dialect.DBType() == schemas.MSSQL { sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) - } else if session.engine.dialect.DBType() == core.ORACLE { + } else if session.engine.dialect.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) } args = condArgs } else { - if session.engine.dialect.DBType() == core.MSSQL { + if session.engine.dialect.DBType() == schemas.MSSQL { sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) - } else if session.engine.dialect.DBType() == core.ORACLE { + } else if session.engine.dialect.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) diff --git a/session_find.go b/session_find.go index c7043ea6..7ae54a5f 100644 --- a/session_find.go +++ b/session_find.go @@ -11,7 +11,8 @@ import ( "strings" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) const ( @@ -197,7 +198,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) return session.noCacheFind(table, sliceValue, sqlStr, args...) } -func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error { +func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error { rows, err := session.queryRows(sqlStr, args...) if err != nil { return err @@ -236,10 +237,10 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va return reflect.New(elemType) } - var containerValueSetFunc func(*reflect.Value, core.PK) error + var containerValueSetFunc func(*reflect.Value, schemas.PK) error if containerValue.Kind() == reflect.Slice { - containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { + containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error { if isPointer { containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr())) } else { @@ -256,7 +257,7 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va return errors.New("don't support multiple primary key's map has non-slice key type") } - containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { + containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error { keyValue := reflect.New(keyType) err := convertPKToValue(table, keyValue.Interface(), pk) if err != nil { @@ -310,7 +311,7 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va return nil } -func convertPKToValue(table *core.Table, dst interface{}, pk core.PK) error { +func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { cols := table.PKColumns() if len(cols) == 1 { return convertAssign(dst, pk[0]) @@ -343,7 +344,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } table := session.statement.RefTable - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { rows, err := session.queryRows(newsql, args...) if err != nil { @@ -352,7 +353,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in defer rows.Close() var i int - ids = make([]core.PK, 0) + ids = make([]schemas.PK, 0) for rows.Next() { i++ if i > 500 { @@ -364,7 +365,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in if err != nil { return err } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { pk[i], err = session.engine.idTypeAssertion(col, res[i]) if err != nil { @@ -376,7 +377,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } session.engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, sqlStr, newsql, args) - err = core.PutCacheSql(cacher, ids, tableName, newsql, args) + err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return err } @@ -387,7 +388,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) ididxes := make(map[string]int) - var ides []core.PK + var ides []schemas.PK var temps = make([]interface{}, len(ids)) for idx, id := range ids { @@ -502,7 +503,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } } else { if keyType.Kind() != reflect.Slice { - return errors.New("table have multiple primary keys, key is not core.PK or slice") + return errors.New("table have multiple primary keys, key is not schemas.PK or slice") } ikey = key } diff --git a/session_find_test.go b/session_find_test.go index a5c7ae4f..7847e284 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) func TestJoinLimit(t *testing.T) { @@ -300,7 +300,7 @@ func TestOrderSameMapper(t *testing.T) { testEngine.UnMapType(rValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) @@ -325,7 +325,7 @@ func TestHavingSameMapper(t *testing.T) { testEngine.UnMapType(rValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) testEngine.SetMapper(mapper) diff --git a/session_get.go b/session_get.go index cc0a2019..376ac2c1 100644 --- a/session_get.go +++ b/session_get.go @@ -11,7 +11,8 @@ import ( "reflect" "strconv" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) // Get retrieve one record from database, bean's non-empty fields @@ -99,7 +100,7 @@ func (session *Session) get(bean interface{}) (bool, error) { return true, nil } -func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { +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 { return false, err @@ -283,7 +284,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf session.engine.logger.Debug("[cacheGet] find sql:", newsql, args) table := session.statement.RefTable - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { var res = make([]string, len(table.PrimaryKeys)) rows, err := session.NoCache().queryRows(newsql, args...) @@ -301,7 +302,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return false, ErrCacheFailed } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { if col.SQLType.IsText() { pk[i] = res[i] @@ -316,9 +317,9 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } } - ids = []core.PK{pk} + ids = []schemas.PK{pk} session.engine.logger.Debug("[cacheGet] cache ids:", newsql, ids) - err = core.PutCacheSql(cacher, ids, tableName, newsql, args) + err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return false, err } diff --git a/session_get_test.go b/session_get_test.go index 54ba8916..f1e8c7f6 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestGetVar(t *testing.T) { @@ -153,7 +153,7 @@ func TestGetVar(t *testing.T) { assert.Equal(t, "1.5", fmt.Sprintf("%.1f", money)) var money2 float64 - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { has, err = testEngine.SQL("SELECT TOP 1 money FROM " + testEngine.TableName("get_var", true)).Get(&money2) } else { has, err = testEngine.SQL("SELECT money FROM " + testEngine.TableName("get_var", true) + " LIMIT 1").Get(&money2) @@ -233,7 +233,7 @@ func TestGetStruct(t *testing.T) { defer session.Close() var err error - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT userinfo_get ON") @@ -242,7 +242,7 @@ func TestGetStruct(t *testing.T) { cnt, err := session.Insert(&UserinfoGet{Uid: 2}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/session_insert.go b/session_insert.go index fb67db16..4a026b78 100644 --- a/session_insert.go +++ b/session_insert.go @@ -13,7 +13,7 @@ import ( "strings" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // ErrNoElementsOnSlice represents an error there is no element when insert @@ -127,7 +127,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error var colNames []string var colMultiPlaces []string var args []interface{} - var cols []*core.Column + var cols []*schemas.Column for i := 0; i < size; i++ { v := sliceValue.Index(i) @@ -156,7 +156,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsAutoIncrement && isZero(fieldValue.Interface()) { continue } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } if col.IsDeleted { @@ -207,7 +207,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsAutoIncrement && isZero(fieldValue.Interface()) { continue } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } if col.IsDeleted { @@ -251,7 +251,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error cleanupProcessorsClosures(&session.beforeClosures) var sql string - if session.engine.dialect.DBType() == core.ORACLE { + if session.engine.dialect.DBType() == schemas.ORACLE { temp := fmt.Sprintf(") INTO %s (%v) VALUES (", session.engine.Quote(tableName), quoteColumns(colNames, session.engine.Quote, ",")) @@ -358,7 +358,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { var tableName = session.statement.TableName() var output string - if session.engine.dialect.DBType() == core.MSSQL && len(table.AutoIncrement) > 0 { + if session.engine.dialect.DBType() == schemas.MSSQL && len(table.AutoIncrement) > 0 { output = fmt.Sprintf(" OUTPUT Inserted.%s", table.AutoIncrement) } @@ -368,7 +368,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } if len(colPlaces) <= 0 { - if session.engine.dialect.DBType() == core.MYSQL { + if session.engine.dialect.DBType() == schemas.MYSQL { if _, err := buf.WriteString(" VALUES ()"); err != nil { return 0, err } @@ -430,7 +430,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } } - if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == core.POSTGRES { + if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == schemas.POSTGRES { if _, err := buf.WriteString(" RETURNING " + session.engine.Quote(table.AutoIncrement)); err != nil { return 0, err } @@ -469,7 +469,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { // for postgres, many of them didn't implement lastInsertId, so we should // implemented it ourself. - if session.engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 { + if session.engine.dialect.DBType() == schemas.ORACLE && len(table.AutoIncrement) > 0 { res, err := session.queryBytes("select seq_atable.currval from dual", args...) if err != nil { return 0, err @@ -510,7 +510,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue.Set(int64ToIntValue(id, aiValue.Type())) return 1, nil - } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.DBType() == core.POSTGRES || session.engine.dialect.DBType() == core.MSSQL) { + } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.DBType() == schemas.POSTGRES || session.engine.dialect.DBType() == schemas.MSSQL) { res, err := session.queryBytes(sqlStr, args...) if err != nil { @@ -626,7 +626,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac args := make([]interface{}, 0, len(table.ColumnsSeq())) for _, col := range table.Columns() { - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } diff --git a/session_pk_test.go b/session_pk_test.go index 039ab367..ec5a611b 100644 --- a/session_pk_test.go +++ b/session_pk_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) type IntId struct { @@ -726,7 +726,7 @@ func TestCompositeKey(t *testing.T) { } var compositeKeyVal CompositeKey - has, err := testEngine.ID(core.PK{11, 22}).Get(&compositeKeyVal) + has, err := testEngine.ID(schemas.PK{11, 22}).Get(&compositeKeyVal) if err != nil { t.Error(err) } else if !has { @@ -735,7 +735,7 @@ func TestCompositeKey(t *testing.T) { var compositeKeyVal2 CompositeKey // test passing PK ptr, this test seem failed withCache - has, err = testEngine.ID(&core.PK{11, 22}).Get(&compositeKeyVal2) + has, err = testEngine.ID(&schemas.PK{11, 22}).Get(&compositeKeyVal2) if err != nil { t.Error(err) } else if !has { @@ -772,14 +772,14 @@ func TestCompositeKey(t *testing.T) { assert.EqualValues(t, compositeKeyVal, cps[0], "should be equeal") compositeKeyVal = CompositeKey{UpdateStr: "test1"} - cnt, err = testEngine.ID(core.PK{11, 22}).Update(&compositeKeyVal) + cnt, err = testEngine.ID(schemas.PK{11, 22}).Update(&compositeKeyVal) if err != nil { t.Error(err) } else if cnt != 1 { t.Error(errors.New("can't update CompositeKey{11, 22}")) } - cnt, err = testEngine.ID(core.PK{11, 22}).Delete(&CompositeKey{}) + cnt, err = testEngine.ID(schemas.PK{11, 22}).Delete(&CompositeKey{}) if err != nil { t.Error(err) } else if cnt != 1 { @@ -823,7 +823,7 @@ func TestCompositeKey2(t *testing.T) { } var user User - has, err := testEngine.ID(core.PK{"11", 22}).Get(&user) + has, err := testEngine.ID(schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -831,7 +831,7 @@ func TestCompositeKey2(t *testing.T) { } // test passing PK ptr, this test seem failed withCache - has, err = testEngine.ID(&core.PK{"11", 22}).Get(&user) + has, err = testEngine.ID(&schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -839,14 +839,14 @@ func TestCompositeKey2(t *testing.T) { } user = User{NickName: "test1"} - cnt, err = testEngine.ID(core.PK{"11", 22}).Update(&user) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Update(&user) if err != nil { t.Error(err) } else if cnt != 1 { t.Error(errors.New("can't update User{11, 22}")) } - cnt, err = testEngine.ID(core.PK{"11", 22}).Delete(&User{}) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Delete(&User{}) if err != nil { t.Error(err) } else if cnt != 1 { @@ -891,7 +891,7 @@ func TestCompositeKey3(t *testing.T) { } var user UserPK2 - has, err := testEngine.ID(core.PK{"11", 22}).Get(&user) + has, err := testEngine.ID(schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -899,7 +899,7 @@ func TestCompositeKey3(t *testing.T) { } // test passing PK ptr, this test seem failed withCache - has, err = testEngine.ID(&core.PK{"11", 22}).Get(&user) + has, err = testEngine.ID(&schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -907,14 +907,14 @@ func TestCompositeKey3(t *testing.T) { } user = UserPK2{NickName: "test1"} - cnt, err = testEngine.ID(core.PK{"11", 22}).Update(&user) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Update(&user) if err != nil { t.Error(err) } else if cnt != 1 { t.Error(errors.New("can't update User{11, 22}")) } - cnt, err = testEngine.ID(core.PK{"11", 22}).Delete(&UserPK2{}) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Delete(&UserPK2{}) if err != nil { t.Error(err) } else if cnt != 1 { @@ -1130,7 +1130,7 @@ func TestCompositePK(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1+len(tables1), len(tables2)) - var table *core.Table + var table *schemas.Table for _, t := range tables2 { if t.Name == testEngine.GetTableMapper().Obj2Table("TaskSolution") { table = t diff --git a/session_query.go b/session_query.go index 21c00b8d..0623c90c 100644 --- a/session_query.go +++ b/session_query.go @@ -12,7 +12,8 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { @@ -116,8 +117,8 @@ func value2String(rawValue *reflect.Value) (str string, err error) { } // time type case reflect.Struct: - if aa.ConvertibleTo(core.TimeType) { - str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) + 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()) } diff --git a/session_query_test.go b/session_query_test.go index 7e72357f..e4635d64 100644 --- a/session_query_test.go +++ b/session_query_test.go @@ -11,7 +11,7 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" "github.com/stretchr/testify/assert" ) @@ -207,7 +207,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0]["msg"]) } else { assert.EqualValues(t, "0", records[0]["msg"]) @@ -217,7 +217,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0]["msg"]) } else { assert.EqualValues(t, "0", records[0]["msg"]) @@ -244,7 +244,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0][1]) } else { assert.EqualValues(t, "0", records[0][1]) @@ -254,7 +254,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0][1]) } else { assert.EqualValues(t, "0", records[0][1]) diff --git a/session_raw.go b/session_raw.go index a9298296..4a2e2777 100644 --- a/session_raw.go +++ b/session_raw.go @@ -10,7 +10,7 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/core" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { diff --git a/session_schema.go b/session_schema.go index d4c71374..31b2ea42 100644 --- a/session_schema.go +++ b/session_schema.go @@ -9,7 +9,7 @@ import ( "fmt" "strings" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // Ping test if database is ok @@ -125,7 +125,7 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { tableName := session.engine.TableName(beanOrTableName) var needDrop = true if !session.engine.dialect.SupportDropIfExists() { - sqlStr, args := session.engine.dialect.TableCheckSql(tableName) + sqlStr, args := session.engine.dialect.TableCheckSQL(tableName) results, err := session.queryBytes(sqlStr, args...) if err != nil { return err @@ -134,7 +134,7 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { } if needDrop { - sqlStr := session.engine.Dialect().DropTableSql(session.engine.TableName(tableName, true)) + sqlStr := session.engine.Dialect().DropTableSQL(session.engine.TableName(tableName, true)) _, err := session.exec(sqlStr) return err } @@ -153,7 +153,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) } func (session *Session) isTableExist(tableName string) (bool, error) { - sqlStr, args := session.engine.dialect.TableCheckSql(tableName) + sqlStr, args := session.engine.dialect.TableCheckSQL(tableName) results, err := session.queryBytes(sqlStr, args...) return len(results) > 0, err } @@ -190,9 +190,9 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo for _, index := range indexes { if sliceEq(index.Cols, cols) { if unique { - return index.Type == core.UniqueType, nil + return index.Type == schemas.UniqueType, nil } - return index.Type == core.IndexType, nil + return index.Type == schemas.IndexType, nil } } return false, nil @@ -207,14 +207,14 @@ func (session *Session) addColumn(colName string) error { func (session *Session) addIndex(tableName, idxName string) error { index := session.statement.RefTable.Indexes[idxName] - sqlStr := session.engine.dialect.CreateIndexSql(tableName, index) + sqlStr := session.engine.dialect.CreateIndexSQL(tableName, index) _, err := session.exec(sqlStr) return err } func (session *Session) addUnique(tableName, uqeName string) error { index := session.statement.RefTable.Indexes[uqeName] - sqlStr := session.engine.dialect.CreateIndexSql(tableName, index) + sqlStr := session.engine.dialect.CreateIndexSQL(tableName, index) _, err := session.exec(sqlStr) return err } @@ -253,7 +253,7 @@ func (session *Session) Sync2(beans ...interface{}) error { } tbNameWithSchema := engine.tbNameWithSchema(tbName) - var oriTable *core.Table + var oriTable *schemas.Table for _, tb := range tables { if strings.EqualFold(engine.tbNameWithSchema(tb.Name), engine.tbNameWithSchema(tbName)) { oriTable = tb @@ -287,7 +287,7 @@ func (session *Session) Sync2(beans ...interface{}) error { // check columns for _, col := range table.Columns() { - var oriCol *core.Column + var oriCol *schemas.Column for _, col2 := range oriTable.Columns() { if strings.EqualFold(col.Name, col2.Name) { oriCol = col2 @@ -306,27 +306,27 @@ func (session *Session) Sync2(beans ...interface{}) error { } err = nil - expectedType := engine.dialect.SqlType(col) - curType := engine.dialect.SqlType(oriCol) + expectedType := engine.dialect.SQLType(col) + curType := engine.dialect.SQLType(oriCol) if expectedType != curType { - if expectedType == core.Text && - strings.HasPrefix(curType, core.Varchar) { + if expectedType == schemas.Text && + strings.HasPrefix(curType, schemas.Varchar) { // currently only support mysql & postgres - if engine.dialect.DBType() == core.MYSQL || - engine.dialect.DBType() == core.POSTGRES { + if engine.dialect.DBType() == schemas.MYSQL || + engine.dialect.DBType() == schemas.POSTGRES { engine.logger.Infof("Table %s column %s change type from %s to %s\n", tbNameWithSchema, col.Name, curType, expectedType) - _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } else { engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n", tbNameWithSchema, col.Name, curType, expectedType) } - } else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) { - if engine.dialect.DBType() == core.MYSQL { + } else if strings.HasPrefix(curType, schemas.Varchar) && strings.HasPrefix(expectedType, schemas.Varchar) { + if engine.dialect.DBType() == schemas.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", tbNameWithSchema, col.Name, oriCol.Length, col.Length) - _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } } } else { @@ -335,12 +335,12 @@ func (session *Session) Sync2(beans ...interface{}) error { tbNameWithSchema, col.Name, curType, expectedType) } } - } else if expectedType == core.Varchar { - if engine.dialect.DBType() == core.MYSQL { + } else if expectedType == schemas.Varchar { + if engine.dialect.DBType() == schemas.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", tbNameWithSchema, col.Name, oriCol.Length, col.Length) - _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } } } @@ -348,7 +348,7 @@ func (session *Session) Sync2(beans ...interface{}) error { if col.Default != oriCol.Default { switch { case col.IsAutoIncrement: // For autoincrement column, don't check default - case (col.SQLType.Name == core.Bool || col.SQLType.Name == core.Boolean) && + case (col.SQLType.Name == schemas.Bool || col.SQLType.Name == schemas.Boolean) && ((strings.EqualFold(col.Default, "true") && oriCol.Default == "1") || (strings.EqualFold(col.Default, "false") && oriCol.Default == "0")): default: @@ -367,10 +367,10 @@ func (session *Session) Sync2(beans ...interface{}) error { } var foundIndexNames = make(map[string]bool) - var addedNames = make(map[string]*core.Index) + var addedNames = make(map[string]*schemas.Index) for name, index := range table.Indexes { - var oriIndex *core.Index + var oriIndex *schemas.Index for name2, index2 := range oriTable.Indexes { if index.Equal(index2) { oriIndex = index2 @@ -381,7 +381,7 @@ func (session *Session) Sync2(beans ...interface{}) error { if oriIndex != nil { if oriIndex.Type != index.Type { - sql := engine.dialect.DropIndexSql(tbNameWithSchema, oriIndex) + sql := engine.dialect.DropIndexSQL(tbNameWithSchema, oriIndex) _, err = session.exec(sql) if err != nil { return err @@ -397,7 +397,7 @@ func (session *Session) Sync2(beans ...interface{}) error { for name2, index2 := range oriTable.Indexes { if _, ok := foundIndexNames[name2]; !ok { - sql := engine.dialect.DropIndexSql(tbNameWithSchema, index2) + sql := engine.dialect.DropIndexSQL(tbNameWithSchema, index2) _, err = session.exec(sql) if err != nil { return err @@ -406,11 +406,11 @@ func (session *Session) Sync2(beans ...interface{}) error { } for name, index := range addedNames { - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { session.statement.RefTable = table session.statement.tableName = tbNameWithSchema err = session.addUnique(tbNameWithSchema, name) - } else if index.Type == core.IndexType { + } else if index.Type == schemas.IndexType { session.statement.RefTable = table session.statement.tableName = tbNameWithSchema err = session.addIndex(tbNameWithSchema, name) diff --git a/session_schema_test.go b/session_schema_test.go index f24d1405..86f9315e 100644 --- a/session_schema_test.go +++ b/session_schema_test.go @@ -213,7 +213,7 @@ func TestCustomTableName(t *testing.T) { func TestDump(t *testing.T) { assert.NoError(t, prepareEngine()) - fp := testEngine.Dialect().URI().DbName + ".sql" + fp := testEngine.Dialect().URI().DBName + ".sql" os.Remove(fp) assert.NoError(t, testEngine.DumpAllToFile(fp)) } diff --git a/session_tx_test.go b/session_tx_test.go index 7c66b7c7..da3f0f04 100644 --- a/session_tx_test.go +++ b/session_tx_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) func TestTransaction(t *testing.T) { @@ -89,7 +89,7 @@ func TestCombineTransactionSameMapper(t *testing.T) { oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(rValue(new(Userinfo)).Type()) - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) testEngine.SetMapper(oldMapper) diff --git a/session_update.go b/session_update.go index ceaade46..7316c3b2 100644 --- a/session_update.go +++ b/session_update.go @@ -12,10 +12,11 @@ import ( "strings" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) -func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, args ...interface{}) error { +func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { return ErrCacheFailed @@ -42,7 +43,7 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, cacher := session.engine.getCacher(tableName) session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) - ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { rows, err := session.NoCache().queryRows(newsql, args[nStart:]...) if err != nil { @@ -50,14 +51,14 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, } defer rows.Close() - ids = make([]core.PK, 0) + ids = make([]schemas.PK, 0) for rows.Next() { var res = make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { return err } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { if col.SQLType.IsNumeric() { n, err := strconv.ParseInt(res[i], 10, 64) @@ -339,9 +340,9 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var top string if st.LimitN != nil { limitValue := *st.LimitN - if st.Engine.dialect.DBType() == core.MYSQL { + if st.Engine.dialect.DBType() == schemas.MYSQL { condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) - } else if st.Engine.dialect.DBType() == core.SQLITE { + } else if st.Engine.dialect.DBType() == schemas.SQLITE { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -352,7 +353,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.Engine.dialect.DBType() == core.POSTGRES { + } else if st.Engine.dialect.DBType() == schemas.POSTGRES { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -364,8 +365,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.Engine.dialect.DBType() == core.MSSQL { - if st.OrderStr != "" && st.Engine.dialect.DBType() == core.MSSQL && + } else if st.Engine.dialect.DBType() == schemas.MSSQL { + if st.OrderStr != "" && st.Engine.dialect.DBType() == schemas.MSSQL && 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], @@ -392,7 +393,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var fromSQL string if session.statement.TableAlias != "" { switch session.engine.dialect.DBType() { - case core.MSSQL: + case schemas.MSSQL: fromSQL = fmt.Sprintf("FROM %s %s ", tableAlias, session.statement.TableAlias) tableAlias = session.statement.TableAlias default: @@ -467,7 +468,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac continue } } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } diff --git a/session_update_test.go b/session_update_test.go index d646ff2d..cb79bad0 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -12,7 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) func TestUpdateMap(t *testing.T) { @@ -691,7 +691,7 @@ func TestUpdateSameMapper(t *testing.T) { testEngine.UnMapType(rValue(new(UpdateAllCols)).Type()) testEngine.UnMapType(rValue(new(UpdateMustCols)).Type()) testEngine.UnMapType(rValue(new(UpdateIncr)).Type()) - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) testEngine.UnMapType(rValue(new(Condi)).Type()) @@ -1419,7 +1419,7 @@ func TestUpdateMap3(t *testing.T) { testEngine.SetColumnMapper(oldMapper) }() - mapper := core.NewPrefixMapper(core.SnakeMapper{}, "F") + mapper := names.NewPrefixMapper(names.SnakeMapper{}, "F") testEngine.SetColumnMapper(mapper) assertSync(t, new(UpdateMapUser)) diff --git a/statement.go b/statement.go index ea3ecabe..87cab7cc 100644 --- a/statement.go +++ b/statement.go @@ -12,16 +12,17 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/dialects" + "xorm.io/xorm/schemas" ) // Statement save all the sql info for executing SQL type Statement struct { - RefTable *core.Table + RefTable *schemas.Table Engine *Engine Start int LimitN *int - idParam *core.PK + idParam *schemas.PK OrderStr string JoinStr string joinArgs []interface{} @@ -266,7 +267,7 @@ func (statement *Statement) buildUpdates(bean interface{}, continue } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } @@ -314,7 +315,7 @@ func (statement *Statement) buildUpdates(bean interface{}, var val interface{} if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { data, err := structConvert.ToDB() if err != nil { engine.logger.Error(err) @@ -325,7 +326,7 @@ func (statement *Statement) buildUpdates(bean interface{}, } } - if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Interface().(Conversion); ok { data, err := structConvert.ToDB() if err != nil { engine.logger.Error(err) @@ -388,8 +389,8 @@ func (statement *Statement) buildUpdates(bean interface{}, t := int64(fieldValue.Uint()) val = reflect.ValueOf(&t).Interface() case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } @@ -496,7 +497,7 @@ func (statement *Statement) needTableName() bool { return len(statement.JoinStr) > 0 } -func (statement *Statement) colName(col *core.Column, tableName string) string { +func (statement *Statement) colName(col *schemas.Column, tableName string) string { if statement.needTableName() { var nm = tableName if len(statement.TableAlias) > 0 { @@ -523,12 +524,12 @@ func (statement *Statement) ID(id interface{}) *Statement { switch idType { case ptrPkType: - if pkPtr, ok := (id).(*core.PK); ok { + if pkPtr, ok := (id).(*schemas.PK); ok { statement.idParam = pkPtr return statement } case pkType: - if pk, ok := (id).(core.PK); ok { + if pk, ok := (id).(schemas.PK); ok { statement.idParam = &pk return statement } @@ -536,11 +537,11 @@ func (statement *Statement) ID(id interface{}) *Statement { switch idType.Kind() { case reflect.String: - statement.idParam = &core.PK{idValue.Convert(reflect.TypeOf("")).Interface()} + statement.idParam = &schemas.PK{idValue.Convert(reflect.TypeOf("")).Interface()} return statement } - statement.idParam = &core.PK{id} + statement.idParam = &schemas.PK{id} return statement } @@ -807,7 +808,7 @@ func (statement *Statement) genColumnStr() string { continue } - if col.MapType == core.ONLYTODB { + if col.MapType == schemas.ONLYTODB { continue } @@ -832,7 +833,7 @@ func (statement *Statement) genColumnStr() string { } func (statement *Statement) genCreateTableSQL() string { - return statement.Engine.dialect.CreateTableSql(statement.RefTable, statement.TableName(), + return statement.Engine.dialect.CreateTableSQL(statement.RefTable, statement.TableName(), statement.StoreEngine, statement.Charset) } @@ -840,8 +841,8 @@ func (statement *Statement) genIndexSQL() []string { var sqls []string tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { - if index.Type == core.IndexType { - sql := statement.Engine.dialect.CreateIndexSql(tbName, index) + if index.Type == schemas.IndexType { + sql := statement.Engine.dialect.CreateIndexSQL(tbName, index) /*idxTBName := strings.Replace(tbName, ".", "_", -1) idxTBName = strings.Replace(idxTBName, `"`, "", -1) sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(idxTBName, idxName)), @@ -860,8 +861,8 @@ func (statement *Statement) genUniqueSQL() []string { var sqls []string tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { - if index.Type == core.UniqueType { - sql := statement.Engine.dialect.CreateIndexSql(tbName, index) + if index.Type == schemas.UniqueType { + sql := statement.Engine.dialect.CreateIndexSQL(tbName, index) sqls = append(sqls, sql) } } @@ -871,13 +872,17 @@ func (statement *Statement) genUniqueSQL() []string { func (statement *Statement) genDelIndexSQL() []string { var sqls []string tbName := statement.TableName() + idx := strings.Index(tbName, ".") + if idx > -1 { + tbName = tbName[idx+1:] + } idxPrefixName := strings.Replace(tbName, `"`, "", -1) idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1) for idxName, index := range statement.RefTable.Indexes { var rIdxName string - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { rIdxName = uniqueName(idxPrefixName, idxName) - } else if index.Type == core.IndexType { + } else if index.Type == schemas.IndexType { rIdxName = indexName(idxPrefixName, idxName) } sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(statement.Engine.TableName(rIdxName, true))) @@ -889,18 +894,18 @@ func (statement *Statement) genDelIndexSQL() []string { return sqls } -func (statement *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { +func (statement *Statement) genAddColumnStr(col *schemas.Column) (string, []interface{}) { quote := statement.Engine.Quote sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quote(statement.TableName()), - col.String(statement.Engine.dialect)) - if statement.Engine.dialect.DBType() == core.MYSQL && len(col.Comment) > 0 { + dialects.String(statement.Engine.dialect, col)) + if statement.Engine.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { sql += " COMMENT '" + col.Comment + "'" } sql += ";" return sql, []interface{}{} } -func (statement *Statement) buildConds(table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { +func (statement *Statement) buildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { return statement.Engine.buildConds(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) } @@ -1054,14 +1059,14 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n whereStr = " WHERE " + condSQL } - if dialect.DBType() == core.MSSQL && strings.Contains(statement.TableName(), "..") { + if dialect.DBType() == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { fromStr += statement.TableName() } else { fromStr += quote(statement.TableName()) } if statement.TableAlias != "" { - if dialect.DBType() == core.ORACLE { + if dialect.DBType() == schemas.ORACLE { fromStr += " " + quote(statement.TableAlias) } else { fromStr += " AS " + quote(statement.TableAlias) @@ -1072,7 +1077,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n } pLimitN := statement.LimitN - if dialect.DBType() == core.MSSQL { + if dialect.DBType() == schemas.MSSQL { if pLimitN != nil { LimitNValue := *pLimitN top = fmt.Sprintf("TOP %d ", LimitNValue) @@ -1134,7 +1139,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) } if needLimit { - if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE { + if dialect.DBType() != schemas.MSSQL && dialect.DBType() != schemas.ORACLE { if statement.Start > 0 { if pLimitN != nil { fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", *pLimitN, statement.Start) @@ -1144,7 +1149,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n } else if pLimitN != nil { fmt.Fprint(&buf, " LIMIT ", *pLimitN) } - } else if dialect.DBType() == core.ORACLE { + } else if dialect.DBType() == schemas.ORACLE { if statement.Start != 0 || pLimitN != nil { oldString := buf.String() buf.Reset() @@ -1158,7 +1163,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n } } if statement.IsForUpdate { - return dialect.ForUpdateSql(buf.String()), nil + return dialect.ForUpdateSQL(buf.String()), nil } return buf.String(), nil @@ -1183,7 +1188,7 @@ func (statement *Statement) processIDParam() error { return nil } -func (statement *Statement) joinColumns(cols []*core.Column, includeTableName bool) string { +func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName bool) string { var colnames = make([]string, len(cols)) for i, col := range cols { if includeTableName { @@ -1211,7 +1216,7 @@ func (statement *Statement) convertIDSQL(sqlStr string) string { var top string pLimitN := statement.LimitN - if pLimitN != nil && statement.Engine.dialect.DBType() == core.MSSQL { + if pLimitN != nil && statement.Engine.dialect.DBType() == schemas.MSSQL { top = fmt.Sprintf("TOP %d ", *pLimitN) } @@ -1240,9 +1245,9 @@ func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { // TODO: for postgres only, if any other database? var paraStr string - if statement.Engine.dialect.DBType() == core.POSTGRES { + if statement.Engine.dialect.DBType() == schemas.POSTGRES { paraStr = "$" - } else if statement.Engine.dialect.DBType() == core.MSSQL { + } else if statement.Engine.dialect.DBType() == schemas.MSSQL { paraStr = ":" } diff --git a/statement_args.go b/statement_args.go index 310f24d6..4f35ce6e 100644 --- a/statement_args.go +++ b/statement_args.go @@ -11,7 +11,7 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func quoteNeeded(a interface{}) bool { @@ -80,7 +80,7 @@ const insertSelectPlaceHolder = true func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case bool: - if statement.Engine.dialect.DBType() == core.MSSQL { + if statement.Engine.dialect.DBType() == schemas.MSSQL { if argv { if _, err := w.WriteString("1"); err != nil { return err @@ -119,7 +119,7 @@ func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) er w.Append(arg) } else { var convertFunc = convertStringSingleQuote - if statement.Engine.dialect.DBType() == core.MYSQL { + if statement.Engine.dialect.DBType() == schemas.MYSQL { convertFunc = convertString } if _, err := w.WriteString(convertArg(arg, convertFunc)); err != nil { diff --git a/statement_test.go b/statement_test.go index acc542ab..bc5fc5dd 100644 --- a/statement_test.go +++ b/statement_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) var colStrTests = []struct { @@ -42,7 +42,7 @@ func TestColumnsStringGeneration(t *testing.T) { columns := statement.RefTable.Columns() if testCase.onlyToDBColumnNdx >= 0 { - columns[testCase.onlyToDBColumnNdx].MapType = core.ONLYTODB + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB } actual := statement.genColumnStr() @@ -51,7 +51,7 @@ func TestColumnsStringGeneration(t *testing.T) { t.Errorf("[test #%d] Unexpected columns string:\nwant:\t%s\nhave:\t%s", ndx, testCase.expected, actual) } if testCase.onlyToDBColumnNdx >= 0 { - columns[testCase.onlyToDBColumnNdx].MapType = core.TWOSIDES + columns[testCase.onlyToDBColumnNdx].MapType = schemas.TWOSIDES } } } @@ -69,7 +69,7 @@ func BenchmarkColumnsStringGeneration(b *testing.B) { if testCase.onlyToDBColumnNdx >= 0 { columns := statement.RefTable.Columns() - columns[testCase.onlyToDBColumnNdx].MapType = core.ONLYTODB // !nemec784! Column must be skipped + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB // !nemec784! Column must be skipped } b.StartTimer() @@ -88,7 +88,7 @@ func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { b.StopTimer() mapCols := make(map[string]bool) - cols := []*core.Column{ + cols := []*schemas.Column{ {Name: `ID`}, {Name: `IsDeleted`}, {Name: `Caption`}, @@ -122,7 +122,7 @@ func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { b.StopTimer() mapCols := make(map[string]bool) - cols := []*core.Column{ + cols := []*schemas.Column{ {Name: `ID`}, {Name: `IsDeleted`}, {Name: `Caption`}, diff --git a/tag.go b/tag.go index 2add6485..92fcdb5e 100644 --- a/tag.go +++ b/tag.go @@ -11,15 +11,36 @@ import ( "strings" "time" - "xorm.io/core" + "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 + } + } + } + if lastIdx < len(tag) { + tags = append(tags, strings.TrimSpace(tag[lastIdx:])) + } + return +} + +// tagContext represents a context for xorm tag parse. type tagContext struct { tagName string params []string preTag, nextTag string - table *core.Table - col *core.Column + table *schemas.Table + col *schemas.Column fieldValue reflect.Value isIndex bool isUnique bool @@ -59,7 +80,7 @@ var ( ) func init() { - for k := range core.SqlTypes { + for k := range schemas.SqlTypes { defaultTagHandlers[k] = SQLTypeTagHandler } } @@ -71,13 +92,13 @@ func IgnoreTagHandler(ctx *tagContext) error { // OnlyFromDBTagHandler describes mapping direction tag handler func OnlyFromDBTagHandler(ctx *tagContext) error { - ctx.col.MapType = core.ONLYFROMDB + ctx.col.MapType = schemas.ONLYFROMDB return nil } // OnlyToDBTagHandler describes mapping direction tag handler func OnlyToDBTagHandler(ctx *tagContext) error { - ctx.col.MapType = core.ONLYTODB + ctx.col.MapType = schemas.ONLYTODB return nil } @@ -177,7 +198,7 @@ func DeletedTagHandler(ctx *tagContext) error { // IndexTagHandler describes index tag handler func IndexTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { - ctx.indexNames[ctx.params[0]] = core.IndexType + ctx.indexNames[ctx.params[0]] = schemas.IndexType } else { ctx.isIndex = true } @@ -187,7 +208,7 @@ func IndexTagHandler(ctx *tagContext) error { // UniqueTagHandler describes unique tag handler func UniqueTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { - ctx.indexNames[ctx.params[0]] = core.UniqueType + ctx.indexNames[ctx.params[0]] = schemas.UniqueType } else { ctx.isUnique = true } @@ -204,16 +225,16 @@ func CommentTagHandler(ctx *tagContext) error { // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *tagContext) error { - ctx.col.SQLType = core.SQLType{Name: ctx.tagName} + ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName} if len(ctx.params) > 0 { - if ctx.tagName == core.Enum { + 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 } - } else if ctx.tagName == core.Set { + } else if ctx.tagName == schemas.Set { ctx.col.SetOptions = make(map[string]int) for k, v := range ctx.params { v = strings.TrimSpace(v) diff --git a/tag_extends_test.go b/tag_extends_test.go index b23b8181..80540b57 100644 --- a/tag_extends_test.go +++ b/tag_extends_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) type tempUser struct { @@ -269,7 +269,7 @@ func TestExtends2(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -279,7 +279,7 @@ func TestExtends2(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -339,7 +339,7 @@ func TestExtends3(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -348,7 +348,7 @@ func TestExtends3(t *testing.T) { _, err = session.Insert(&msg) assert.NoError(t, err) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -433,7 +433,7 @@ func TestExtends4(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -442,7 +442,7 @@ func TestExtends4(t *testing.T) { _, err = session.Insert(&msg) assert.NoError(t, err) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/tag_id_test.go b/tag_id_test.go index dce5f688..a386b783 100644 --- a/tag_id_test.go +++ b/tag_id_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) type IDGonicMapper struct { @@ -20,7 +20,7 @@ func TestGonicMapperID(t *testing.T) { oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) - testEngine.SetMapper(core.LintGonicMapper) + testEngine.SetMapper(names.LintGonicMapper) defer func() { testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) testEngine.SetMapper(oldMapper) @@ -57,7 +57,7 @@ func TestSameMapperID(t *testing.T) { oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) testEngine.SetMapper(oldMapper) diff --git a/tag_test.go b/tag_test.go index 979ba929..894addac 100644 --- a/tag_test.go +++ b/tag_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) type UserCU struct { @@ -203,7 +203,7 @@ func TestAutoIncrTag(t *testing.T) { func TestTagComment(t *testing.T) { assert.NoError(t, prepareEngine()) // FIXME: only support mysql - if testEngine.Dialect().DriverName() != core.MYSQL { + if testEngine.Dialect().DriverName() != schemas.MYSQL { return } diff --git a/types.go b/types.go index c76a5460..ee725dae 100644 --- a/types.go +++ b/types.go @@ -7,10 +7,10 @@ package xorm import ( "reflect" - "xorm.io/core" + "xorm.io/xorm/schemas" ) var ( - ptrPkType = reflect.TypeOf(&core.PK{}) - pkType = reflect.TypeOf(core.PK{}) + ptrPkType = reflect.TypeOf(&schemas.PK{}) + pkType = reflect.TypeOf(schemas.PK{}) ) diff --git a/types_test.go b/types_test.go index c979ded4..1e21907c 100644 --- a/types_test.go +++ b/types_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestArrayField(t *testing.T) { @@ -137,8 +137,8 @@ type ConvStruct struct { Conv ConvString Conv2 *ConvString Cfg1 ConvConfig - Cfg2 *ConvConfig `xorm:"TEXT"` - Cfg3 core.Conversion `xorm:"BLOB"` + Cfg2 *ConvConfig `xorm:"TEXT"` + Cfg3 Conversion `xorm:"BLOB"` Slice SliceType } @@ -267,7 +267,7 @@ type Status struct { } var ( - _ core.Conversion = &Status{} + _ Conversion = &Status{} Registered Status = Status{"Registered", "white"} Approved Status = Status{"Approved", "green"} Removed Status = Status{"Removed", "red"} @@ -311,7 +311,7 @@ func TestCustomType2(t *testing.T) { session := testEngine.NewSession() defer session.Close() - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("set IDENTITY_INSERT " + tableName + " on") @@ -322,7 +322,7 @@ func TestCustomType2(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/xorm.go b/xorm.go index e1c83b56..e7e98739 100644 --- a/xorm.go +++ b/xorm.go @@ -15,7 +15,12 @@ import ( "sync" "time" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/core" + "xorm.io/xorm/dialects" + "xorm.io/xorm/log" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) const ( @@ -23,44 +28,14 @@ const ( Version string = "0.8.0.1015" ) -func regDrvsNDialects() bool { - providedDrvsNDialects := map[string]struct { - dbType core.DbType - getDriver func() core.Driver - getDialect func() core.Dialect - }{ - "mssql": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, - "odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access - "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, - "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, - "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, - "pgx": {"postgres", func() core.Driver { return &pqDriverPgx{} }, func() core.Dialect { return &postgres{} }}, - "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, - "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, - "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }}, - } - - for driverName, v := range providedDrvsNDialects { - if driver := core.QueryDriver(driverName); driver == nil { - core.RegisterDriver(driverName, v.getDriver()) - core.RegisterDialect(v.dbType, v.getDialect) - } - } - return true -} - func close(engine *Engine) { engine.Close() } -func init() { - regDrvsNDialects() -} - // NewEngine new a db manager according to the parameter. Currently support four // drivers func NewEngine(driverName string, dataSourceName string) (*Engine, error) { - driver := core.QueryDriver(driverName) + driver := dialects.QueryDriver(driverName) if driver == nil { return nil, fmt.Errorf("Unsupported driver name: %v", driverName) } @@ -70,9 +45,9 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { return nil, err } - dialect := core.QueryDialect(uri.DbType) + dialect := dialects.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) } db, err := core.Open(driverName, dataSourceName) @@ -88,32 +63,32 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { engine := &Engine{ db: db, dialect: dialect, - Tables: make(map[reflect.Type]*core.Table), + Tables: make(map[reflect.Type]*schemas.Table), mutex: &sync.RWMutex{}, TagIdentifier: "xorm", TZLocation: time.Local, tagHandlers: defaultTagHandlers, - cachers: make(map[string]core.Cacher), + cachers: make(map[string]caches.Cacher), defaultContext: context.Background(), } - if uri.DbType == core.SQLITE { + if uri.DBType == schemas.SQLITE { engine.DatabaseTZ = time.UTC } else { engine.DatabaseTZ = time.Local } - logger := NewSimpleLogger(os.Stdout) - logger.SetLevel(core.LOG_INFO) + logger := log.NewSimpleLogger(os.Stdout) + logger.SetLevel(log.LOG_INFO) engine.SetLogger(logger) - engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper))) + engine.SetMapper(names.NewCacheMapper(new(names.SnakeMapper))) runtime.SetFinalizer(engine, close) return engine, nil } -// NewEngineWithParams new a db manager with params. The params will be passed to dialect. +// NewEngineWithParams new a db manager with params. The params will be passed to dialects. func NewEngineWithParams(driverName string, dataSourceName string, params map[string]string) (*Engine, error) { engine, err := NewEngine(driverName, dataSourceName) engine.dialect.SetParams(params) diff --git a/xorm_test.go b/xorm_test.go index 21715256..2a24edb3 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -8,7 +8,6 @@ import ( "database/sql" "flag" "fmt" - "log" "os" "strings" "testing" @@ -18,7 +17,10 @@ import ( _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" _ "github.com/ziutek/mymysql/godrv" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/log" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) var ( @@ -30,14 +32,14 @@ var ( showSQL = flag.Bool("show_sql", true, "show generated SQLs") ptrConnStr = flag.String("conn_str", "./test.db?cache=shared&mode=rwc", "test database connection string") mapType = flag.String("map_type", "snake", "indicate the name mapping") - cache = flag.Bool("cache", false, "if enable cache") + cacheFlag = flag.Bool("cache", false, "if enable cache") cluster = flag.Bool("cluster", false, "if this is a cluster") splitter = flag.String("splitter", ";", "the splitter on connstr for cluster") schema = flag.String("schema", "", "specify the schema") ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb") - tableMapper core.IMapper - colMapper core.IMapper + tableMapper names.Mapper + colMapper names.Mapper ) func createEngine(dbType, connStr string) error { @@ -46,7 +48,7 @@ func createEngine(dbType, connStr string) error { if !*cluster { switch strings.ToLower(dbType) { - case core.MSSQL: + case schemas.MSSQL: db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "master", -1)) if err != nil { return err @@ -56,7 +58,7 @@ func createEngine(dbType, connStr string) error { } db.Close() *ignoreSelectUpdate = true - case core.POSTGRES: + case schemas.POSTGRES: db, err := sql.Open(dbType, connStr) if err != nil { return err @@ -79,7 +81,7 @@ func createEngine(dbType, connStr string) error { } db.Close() *ignoreSelectUpdate = true - case core.MYSQL: + case schemas.MYSQL: db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "mysql", -1)) if err != nil { return err @@ -107,20 +109,20 @@ func createEngine(dbType, connStr string) error { testEngine.SetSchema(*schema) } testEngine.ShowSQL(*showSQL) - testEngine.SetLogLevel(core.LOG_DEBUG) - if *cache { - cacher := NewLRUCacher(NewMemoryStore(), 100000) + testEngine.SetLogLevel(log.LOG_DEBUG) + if *cacheFlag { + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 100000) testEngine.SetDefaultCacher(cacher) } if len(*mapType) > 0 { switch *mapType { case "snake": - testEngine.SetMapper(core.SnakeMapper{}) + testEngine.SetMapper(names.SnakeMapper{}) case "same": - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) case "gonic": - testEngine.SetMapper(core.LintGonicMapper) + testEngine.SetMapper(names.LintGonicMapper) } } } @@ -158,7 +160,7 @@ func TestMain(m *testing.M) { } } else { if ptrConnStr == nil { - log.Fatal("you should indicate conn string") + fmt.Println("you should indicate conn string") return } connString = *ptrConnStr @@ -175,7 +177,7 @@ func TestMain(m *testing.M) { fmt.Println("testing", dbType, connString) if err := prepareEngine(); err != nil { - log.Fatal(err) + fmt.Println(err) return } From a90b7a48b4e51986a5254d7bd12bbc4003fb4098 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 10:03:48 +0000 Subject: [PATCH 026/112] Fix mssql quote (#1535) Fix some quotes Fix mssql quote Merge core package back into the main repository and split into serval sub packages. (#1543) Fix test Improve fmt update go.mod Move core as a sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1543 Fix int time deleted bug (#1539) Fix panic Fix test Fix test for mssql time Add sql type check on deleted cond Fix int time deleted bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1539 Add test for mysql8.0 (#1538) Fix pk order on test Add test for mysql8.0 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1538 Add test for join limit (#1536) Add test for join limit Reviewed-on: https://gitea.com/xorm/xorm/pulls/1536 Improve drone (#1537) Fix drone Improve drone * use traditional positional parameters on inser... Reviewed-on: https://gitea.com/xorm/xorm/pulls/1537 Fix slice of struct not cache bug (#895) Fix failure caused by nil bean Judge both type of struct and pointer in case of out-of-range Fix issue #894 Add test for join subquery (#1528) Fix test Fix subquery with schema Add test for join subquery Add makefile (#1531) Fix drone Fix ci Add deps Improve drone Fix envs Add makefile Reviewed-on: https://gitea.com/xorm/xorm/pulls/1531 Add password for postgres drone image (#1530) Add password for postgres drone image Reviewed-on: https://gitea.com/xorm/xorm/pulls/1530 format time when sqlTypeName is core.Varchar (#1026) fix time test add test for time format sign codes according to contributing rules. format time when sqlTypeName is core.Varchar. Same with core.DateTime or core.TimeStamp Add test for second insert error (#1527) Add test for second insert error Reviewed-on: https://gitea.com/xorm/xorm/pulls/1527 Add tests for table name (#1517) add tests for table name Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix wrong warning log on autoincrement column when sync table (#1525) improve doc Fix wrong warning log on autoincrement column when sync table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1525 Fixed Join strings on func Exist (#1520) fix test fixed Join strings on func Exist Co-authored-by: Tomofumi Kusana Reviewed-on: https://gitea.com/xorm/xorm/pulls/1520 For nullable columns, store nil values as NULL (#531) Merge branch 'master' into jcsalem/fix/nil_ptr_is_nullable fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 For nullable columns, store nil values as NULL fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quo... Reviewed-on: https://gitea.com/xorm/xorm/pulls/1535 --- dialects/dialect.go | 7 +++++-- dialects/mssql.go | 2 +- dialects/mysql.go | 7 ++++--- dialects/oracle.go | 7 ++++--- schemas/index.go | 10 +++++----- schemas/table.go | 20 ++++++++------------ session_find_test.go | 6 ++++-- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index b88d4e61..ac9ff301 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -263,6 +263,7 @@ func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, cha }*/ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { + quotes := db.dialect.Quote("") quote := db.dialect.Quote var unique string var idxName string @@ -272,7 +273,7 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { idxName = index.XName(tableName) return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, quote(idxName), quote(tableName), - quote(strings.Join(index.Cols, quote(",")))) + quote(strings.Join(index.Cols, fmt.Sprintf("%c,%c", quotes[1], quotes[0])))) } func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { @@ -300,6 +301,8 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char sql += b.dialect.Quote(tableName) sql += " (" + quotes := b.dialect.Quote("") + if len(table.ColumnsSeq()) > 0 { pkList := table.PrimaryKeys @@ -319,7 +322,7 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) + sql += b.dialect.Quote(strings.Join(pkList, fmt.Sprintf("%c,%c", quotes[1], quotes[0]))) sql += " ), " } diff --git a/dialects/mssql.go b/dialects/mssql.go index 99b1c782..5a91e9ef 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -287,7 +287,7 @@ func (db *mssql) IsReserved(name string) bool { } func (db *mssql) Quote(name string) string { - return "\"" + name + "\"" + return "[" + name + "]" } func (db *mssql) SupportEngine() bool { diff --git a/dialects/mysql.go b/dialects/mysql.go index 39ed4b83..82d7b5f4 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -507,12 +507,13 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*schemas.Index, error) } func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { - var sql string - sql = "CREATE TABLE IF NOT EXISTS " + var sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { tableName = table.Name } + quotes := db.Quote("") + sql += db.Quote(tableName) sql += " (" @@ -535,7 +536,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += db.Quote(strings.Join(pkList, db.Quote(","))) + sql += db.Quote(strings.Join(pkList, fmt.Sprintf("%c,%c", quotes[1], quotes[0]))) sql += " ), " } diff --git a/dialects/oracle.go b/dialects/oracle.go index 501a22c7..2f903331 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -577,8 +577,7 @@ func (db *oracle) DropTableSQL(tableName string) string { } func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { - var sql string - sql = "CREATE TABLE " + var sql = "CREATE TABLE " if tableName == "" { tableName = table.Name } @@ -598,9 +597,11 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c sql += ", " } + quotes := db.Quote("") + if len(pkList) > 0 { sql += "PRIMARY KEY ( " - sql += db.Quote(strings.Join(pkList, db.Quote(","))) + sql += db.Quote(strings.Join(pkList, fmt.Sprintf("%c,%c", quotes[1], quotes[0]))) sql += " ), " } diff --git a/schemas/index.go b/schemas/index.go index e5738c93..9541250f 100644 --- a/schemas/index.go +++ b/schemas/index.go @@ -23,6 +23,11 @@ type Index struct { Cols []string } +// NewIndex new an index object +func NewIndex(name string, indexType int) *Index { + return &Index{true, name, indexType, make([]string, 0)} +} + func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { @@ -65,8 +70,3 @@ func (index *Index) Equal(dst *Index) bool { } return true } - -// NewIndex new an index object -func NewIndex(name string, indexType int) *Index { - return &Index{true, name, indexType, make([]string, 0)} -} diff --git a/schemas/table.go b/schemas/table.go index b32a276a..44aa8152 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -30,14 +30,6 @@ type Table struct { Comment string } -func (table *Table) Columns() []*Column { - return table.columns -} - -func (table *Table) ColumnsSeq() []string { - return table.columnsSeq -} - func NewEmptyTable() *Table { return NewTable("", nil) } @@ -54,9 +46,16 @@ func NewTable(name string, t reflect.Type) *Table { } } +func (table *Table) Columns() []*Column { + return table.columns +} + +func (table *Table) ColumnsSeq() []string { + return table.columnsSeq +} + func (table *Table) columnsByName(name string) []*Column { n := len(name) - for k := range table.columnsMap { if len(k) != n { continue @@ -69,9 +68,7 @@ func (table *Table) columnsByName(name string) []*Column { } func (table *Table) GetColumn(name string) *Column { - cols := table.columnsByName(name) - if cols != nil { return cols[0] } @@ -81,7 +78,6 @@ func (table *Table) GetColumn(name string) *Column { func (table *Table) GetColumnIdx(name string, idx int) *Column { cols := table.columnsByName(name) - if cols != nil && idx < len(cols) { return cols[idx] } diff --git a/session_find_test.go b/session_find_test.go index 7847e284..991fadf2 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -64,8 +64,10 @@ func TestJoinLimit(t *testing.T) { func assertSync(t *testing.T, beans ...interface{}) { for _, bean := range beans { - assert.NoError(t, testEngine.DropTables(bean)) - assert.NoError(t, testEngine.Sync2(bean)) + t.Run(testEngine.TableName(bean, true), func(t *testing.T) { + assert.NoError(t, testEngine.DropTables(bean)) + assert.NoError(t, testEngine.Sync2(bean)) + }) } } From 3df77142b3219b1fde04362589d0f83ead92a510 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 10:05:10 +0000 Subject: [PATCH 027/112] Move reserve words related files into dialects sub package (#1544) Move reserve words related files into dialects sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1544 --- gen_reserved.sh => dialects/gen_reserved.sh | 0 pg_reserved.txt => dialects/pg_reserved.txt | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename gen_reserved.sh => dialects/gen_reserved.sh (100%) rename pg_reserved.txt => dialects/pg_reserved.txt (100%) diff --git a/gen_reserved.sh b/dialects/gen_reserved.sh similarity index 100% rename from gen_reserved.sh rename to dialects/gen_reserved.sh diff --git a/pg_reserved.txt b/dialects/pg_reserved.txt similarity index 100% rename from pg_reserved.txt rename to dialects/pg_reserved.txt From cc33b2d305e54d4d914ebbc7dd3b0dd6123e5bd2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 25 Feb 2020 00:01:36 +0000 Subject: [PATCH 028/112] Fix join table name quote bug (#1534) Fix test Fix test Add new Quoter object to handle quote Fix join table name quote bug Move reserve words related files into dialects sub package (#1544) Move reserve words related files into dialects sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1544 Fix mssql quote (#1535) Fix some quotes Fix mssql quote Merge core package back into the main repository and split into serval sub packages. (#1543) Fix test Improve fmt update go.mod Move core as a sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1543 Fix int time deleted bug (#1539) Fix panic Fix test Fix test for mssql time Add sql type check on deleted cond Fix int time deleted bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1539 Add test for mysql8.0 (#1538) Fix pk order on test Add test for mysql8.0 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1538 Add test for join limit (#1536) Add test for join limit Reviewed-on: https://gitea.com/xorm/xorm/pulls/1536 Improve drone (#1537) Fix drone Improve drone * use traditional positional parameters on inser... Reviewed-on: https://gitea.com/xorm/xorm/pulls/1537 Fix slice of struct not cache bug (#895) Fix failure caused by nil bean Judge both type of struct and pointer in case of out-of-range Fix issue #894 Add test for join subquery (#1528) Fix test Fix subquery with schema Add test for join subquery Add makefile (#1531) Fix drone Fix ci Add deps Improve drone Fix envs Add makefile Reviewed-on: https://gitea.com/xorm/xorm/pulls/1531 Add password for postgres drone image (#1530) Add password for postgres drone image Reviewed-on: https://gitea.com/xorm/xorm/pulls/1530 format time when sqlTypeName is core.Varchar (#1026) fix time test add test for time format sign codes according to contributing rules. format time when sqlTypeName is core.Varchar. Same with core.DateTime or core.TimeStamp Add test for second insert error (#1527) Add test for second insert error Reviewed-on: https://gitea.com/xorm/xorm/pulls/1527 Add tests for table name (#1517) add tests for table name Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix test (#1526) Fix test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1526 Fix wrong warning log on autoincrement column when sync table (#1525) improve doc Fix wrong warning log on autoincrement column when sync table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1525 Fixed Join strings on func Exist (#1520) fix test fixed Join strings on func Exist Co-authored-by: Tomofumi Kusana Reviewed-on: https://gitea.com/xorm/xorm/pulls/1520 For nullable columns, store nil values as NULL (#531) Merge branch 'master' into jcsalem/fix/nil_ptr_is_nullable fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 For nullable columns, store nil values as NULL fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve c... Reviewed-on: https://gitea.com/xorm/xorm/pulls/1534 --- dialects/dialect.go | 52 +++----- dialects/filter.go | 32 +---- dialects/mssql.go | 8 +- dialects/mysql.go | 12 +- dialects/oracle.go | 13 +- dialects/postgres.go | 10 +- dialects/sqlite3.go | 9 +- engine.go | 59 ++------- schemas/quote.go | 165 ++++++++++++++++++++++++ engine_test.go => schemas/quote_test.go | 12 +- session_cond_test.go | 14 +- session_find_test.go | 18 ++- session_insert_test.go | 20 +-- session_tx_test.go | 15 +-- session_update_test.go | 8 +- statement.go | 7 +- statement_exprparam.go | 3 +- statement_quote.go | 19 --- 18 files changed, 276 insertions(+), 200 deletions(-) create mode 100644 schemas/quote.go rename engine_test.go => schemas/quote_test.go (79%) delete mode 100644 statement_quote.go diff --git a/dialects/dialect.go b/dialects/dialect.go index ac9ff301..3ed867f4 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -45,11 +45,8 @@ type Dialect interface { DataSourceName() string IsReserved(string) bool - Quote(string) string + Quoter() schemas.Quoter - AndStr() string - OrStr() string - EqStr() string RollBackStr() string AutoIncrStr() string @@ -101,7 +98,7 @@ type Base struct { // String generate column description string according dialect func String(d Dialect, col *schemas.Column) string { - sql := d.Quote(col.Name) + " " + sql := d.Quoter().Quote(col.Name) + " " sql += d.SQLType(col) + " " @@ -129,7 +126,7 @@ func String(d Dialect, col *schemas.Column) string { // StringNoPk generate column description string according dialect without primary keys func StringNoPk(d Dialect, col *schemas.Column) string { - sql := d.Quote(col.Name) + " " + sql := d.Quoter().Quote(col.Name) + " " sql += d.SQLType(col) + " " @@ -186,18 +183,6 @@ func (b *Base) DataSourceName() string { return b.dataSourceName } -func (b *Base) AndStr() string { - return "AND" -} - -func (b *Base) OrStr() string { - return "OR" -} - -func (b *Base) EqStr() string { - return "=" -} - func (db *Base) RollBackStr() string { return "ROLL BACK" } @@ -207,7 +192,7 @@ func (db *Base) SupportDropIfExists() bool { } func (db *Base) DropTableSQL(tableName string) string { - quote := db.dialect.Quote + quote := db.dialect.Quoter().Quote return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)) } @@ -226,14 +211,15 @@ func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { } func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { + quote := db.dialect.Quoter().Quote query := fmt.Sprintf( "SELECT %v FROM %v.%v WHERE %v = ? AND %v = ? AND %v = ?", - db.dialect.Quote("COLUMN_NAME"), - db.dialect.Quote("INFORMATION_SCHEMA"), - db.dialect.Quote("COLUMNS"), - db.dialect.Quote("TABLE_SCHEMA"), - db.dialect.Quote("TABLE_NAME"), - db.dialect.Quote("COLUMN_NAME"), + quote("COLUMN_NAME"), + quote("INFORMATION_SCHEMA"), + quote("COLUMNS"), + quote("TABLE_SCHEMA"), + quote("TABLE_NAME"), + quote("COLUMN_NAME"), ) return db.HasRecords(query, db.uri.DBName, tableName, colName) } @@ -263,8 +249,7 @@ func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, cha }*/ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { - quotes := db.dialect.Quote("") - quote := db.dialect.Quote + quoter := db.dialect.Quoter() var unique string var idxName string if index.Type == schemas.UniqueType { @@ -272,12 +257,12 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { } idxName = index.XName(tableName) return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, - quote(idxName), quote(tableName), - quote(strings.Join(index.Cols, fmt.Sprintf("%c,%c", quotes[1], quotes[0])))) + quoter.Quote(idxName), quoter.Quote(tableName), + quoter.Quote(strings.Join(index.Cols, quoter.ReverseQuote(",")))) } func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { - quote := db.dialect.Quote + quote := db.dialect.Quoter().Quote var name string if index.IsRegular { name = index.XName(tableName) @@ -298,11 +283,10 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char tableName = table.Name } - sql += b.dialect.Quote(tableName) + quoter := b.dialect.Quoter() + sql += quoter.Quote(tableName) sql += " (" - quotes := b.dialect.Quote("") - if len(table.ColumnsSeq()) > 0 { pkList := table.PrimaryKeys @@ -322,7 +306,7 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += b.dialect.Quote(strings.Join(pkList, fmt.Sprintf("%c,%c", quotes[1], quotes[0]))) + sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(","))) sql += " ), " } diff --git a/dialects/filter.go b/dialects/filter.go index f7bad1a9..4795edb7 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -21,11 +21,12 @@ type QuoteFilter struct { } func (s *QuoteFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { - dummy := dialect.Quote("") - if len(dummy) != 2 { + quoter := dialect.Quoter() + if quoter.IsEmpty() { return sql } - prefix, suffix := dummy[0], dummy[1] + + prefix, suffix := quoter[0][0], quoter[1][0] raw := []byte(sql) for i, cnt := 0, 0; i < len(raw); i = i + 1 { if raw[i] == '`' { @@ -38,32 +39,7 @@ func (s *QuoteFilter) Do(sql string, dialect Dialect, table *schemas.Table) stri } } return string(raw) -} -// IdFilter filter SQL replace (id) to primary key column name -type IdFilter struct { -} - -type Quoter struct { - dialect Dialect -} - -func NewQuoter(dialect Dialect) *Quoter { - return &Quoter{dialect} -} - -func (q *Quoter) Quote(content string) string { - return q.dialect.Quote(content) -} - -func (i *IdFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { - quoter := NewQuoter(dialect) - if table != nil && len(table.PrimaryKeys) == 1 { - sql = strings.Replace(sql, " `(id)` ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) - sql = strings.Replace(sql, " "+quoter.Quote("(id)")+" ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) - return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) - } - return sql } // SeqFilter filter SQL replace ?, ? ... to $1, $2 ... diff --git a/dialects/mssql.go b/dialects/mssql.go index 5a91e9ef..d473d975 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -286,8 +286,8 @@ func (db *mssql) IsReserved(name string) bool { return ok } -func (db *mssql) Quote(name string) string { - return "[" + name + "]" +func (db *mssql) Quoter() schemas.Quoter { + return schemas.Quoter{"[", "]"} } func (db *mssql) SupportEngine() bool { @@ -503,7 +503,7 @@ func (db *mssql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + tableName + "' ) CREATE TABLE " - sql += db.Quote(tableName) + " (" + sql += db.Quoter().Quote(tableName) + " (" pkList := table.PrimaryKeys @@ -534,7 +534,7 @@ func (db *mssql) ForUpdateSQL(query string) string { } func (db *mssql) Filters() []Filter { - return []Filter{&IdFilter{}, &QuoteFilter{}} + return []Filter{&QuoteFilter{}} } type odbcDriver struct { diff --git a/dialects/mysql.go b/dialects/mysql.go index 82d7b5f4..32dc25b7 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -275,8 +275,8 @@ func (db *mysql) IsReserved(name string) bool { return ok } -func (db *mysql) Quote(name string) string { - return "`" + name + "`" +func (db *mysql) Quoter() schemas.Quoter { + return schemas.Quoter{"`", "`"} } func (db *mysql) SupportEngine() bool { @@ -512,9 +512,9 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch tableName = table.Name } - quotes := db.Quote("") + quoter := db.Quoter() - sql += db.Quote(tableName) + sql += quoter.Quote(tableName) sql += " (" if len(table.ColumnsSeq()) > 0 { @@ -536,7 +536,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += db.Quote(strings.Join(pkList, fmt.Sprintf("%c,%c", quotes[1], quotes[0]))) + sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(","))) sql += " ), " } @@ -562,7 +562,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch } func (db *mysql) Filters() []Filter { - return []Filter{&IdFilter{}} + return []Filter{} } type mymysqlDriver struct { diff --git a/dialects/oracle.go b/dialects/oracle.go index 2f903331..bf9ee2af 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -552,8 +552,8 @@ func (db *oracle) IsReserved(name string) bool { return ok } -func (db *oracle) Quote(name string) string { - return "[" + name + "]" +func (db *oracle) Quoter() schemas.Quoter { + return schemas.Quoter{"[", "]"} } func (db *oracle) SupportEngine() bool { @@ -582,7 +582,8 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c tableName = table.Name } - sql += db.Quote(tableName) + " (" + quoter := db.Quoter() + sql += quoter.Quote(tableName) + " (" pkList := table.PrimaryKeys @@ -597,11 +598,9 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c sql += ", " } - quotes := db.Quote("") - if len(pkList) > 0 { sql += "PRIMARY KEY ( " - sql += db.Quote(strings.Join(pkList, fmt.Sprintf("%c,%c", quotes[1], quotes[0]))) + sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(","))) sql += " ), " } @@ -849,7 +848,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*schemas.Index, error } func (db *oracle) Filters() []Filter { - return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: ":", Start: 1}, &IdFilter{}} + return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: ":", Start: 1}} } type goracleDriver struct { diff --git a/dialects/postgres.go b/dialects/postgres.go index e4f4b89b..f161fdfa 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -859,9 +859,8 @@ func (db *postgres) IsReserved(name string) bool { return ok } -func (db *postgres) Quote(name string) string { - name = strings.Replace(name, ".", `"."`, -1) - return "\"" + name + "\"" +func (db *postgres) Quoter() schemas.Quoter { + return schemas.Quoter{`"`, `"`} } func (db *postgres) AutoIncrStr() string { @@ -911,7 +910,6 @@ func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) strin } func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string { - quote := db.Quote idxName := index.Name tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") @@ -928,7 +926,7 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string if db.uri.Schema != "" { idxName = db.uri.Schema + "." + idxName } - return fmt.Sprintf("DROP INDEX %v", quote(idxName)) + return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName)) } func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { @@ -1161,7 +1159,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*schemas.Index, err } func (db *postgres) Filters() []Filter { - return []Filter{&IdFilter{}, &QuoteFilter{}, &SeqFilter{Prefix: "$", Start: 1}} + return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: "$", Start: 1}} } type pqDriver struct { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index b7ff2147..0fd80b73 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -199,8 +199,8 @@ func (db *sqlite3) IsReserved(name string) bool { return ok } -func (db *sqlite3) Quote(name string) string { - return "`" + name + "`" +func (db *sqlite3) Quoter() schemas.Quoter { + return schemas.Quoter{"`", "`"} } func (db *sqlite3) AutoIncrStr() string { @@ -231,7 +231,6 @@ func (db *sqlite3) TableCheckSQL(tableName string) (string, []interface{}) { func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { // var unique string - quote := db.Quote idxName := index.Name if !strings.HasPrefix(idxName, "UQE_") && @@ -242,7 +241,7 @@ func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } } - return fmt.Sprintf("DROP INDEX %v", quote(idxName)) + return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName)) } func (db *sqlite3) ForUpdateSQL(query string) string { @@ -478,7 +477,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*schemas.Index, erro } func (db *sqlite3) Filters() []Filter { - return []Filter{&IdFilter{}} + return []Filter{} } type sqlite3Driver struct { diff --git a/engine.go b/engine.go index c0136bba..b60234e0 100644 --- a/engine.go +++ b/engine.go @@ -193,10 +193,7 @@ func (engine *Engine) SupportInsertMany() bool { func (engine *Engine) quoteColumns(columnStr string) string { columns := strings.Split(columnStr, ",") - for i := 0; i < len(columns); i++ { - columns[i] = engine.Quote(strings.TrimSpace(columns[i])) - } - return strings.Join(columns, ",") + return engine.dialect.Quoter().Join(columns, ",") } // Quote Use QuoteStr quote the string sql @@ -222,53 +219,13 @@ func (engine *Engine) QuoteTo(buf *strings.Builder, value string) { if value == "" { return } - - quoteTo(buf, engine.dialect.Quote(""), value) -} - -func quoteTo(buf *strings.Builder, quotePair string, value string) { - if len(quotePair) < 2 { // no quote - _, _ = buf.WriteString(value) - return - } - - prefix, suffix := quotePair[0], quotePair[1] - - i := 0 - for i < len(value) { - // start of a token; might be already quoted - if value[i] == '.' { - _ = buf.WriteByte('.') - i++ - } else if value[i] == prefix || value[i] == '`' { - // Has quotes; skip/normalize `name` to prefix+name+sufix - var ch byte - if value[i] == prefix { - ch = suffix - } else { - ch = '`' - } - i++ - _ = buf.WriteByte(prefix) - for ; i < len(value) && value[i] != ch; i++ { - _ = buf.WriteByte(value[i]) - } - _ = buf.WriteByte(suffix) - i++ - } else { - // Requires quotes - _ = buf.WriteByte(prefix) - for ; i < len(value) && value[i] != '.'; i++ { - _ = buf.WriteByte(value[i]) - } - _ = buf.WriteByte(suffix) - } - } + engine.dialect.Quoter().QuoteTo(buf, value) } +/* func (engine *Engine) quote(sql string) string { return engine.dialect.Quote(sql) -} +}*/ // SqlType will be deprecated, please use SQLType instead // @@ -530,8 +487,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...dia } cols := table.ColumnsSeq() - colNames := engine.dialect.Quote(strings.Join(cols, engine.dialect.Quote(", "))) - destColNames := dialect.Quote(strings.Join(cols, dialect.Quote(", "))) + colNames := engine.dialect.Quoter().Join(cols, ", ") + destColNames := dialect.Quoter().Join(cols, ", ") rows, err := engine.DB().Query("SELECT " + colNames + " FROM " + engine.Quote(table.Name)) if err != nil { @@ -546,7 +503,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...dia return err } - _, err = io.WriteString(w, "INSERT INTO "+dialect.Quote(table.Name)+" ("+destColNames+") VALUES (") + _, err = io.WriteString(w, "INSERT INTO "+dialect.Quoter().Quote(table.Name)+" ("+destColNames+") VALUES (") if err != nil { return err } @@ -617,7 +574,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...dia // FIXME: Hack for postgres if string(dialect.DBType()) == schemas.POSTGRES && table.AutoIncrColumn() != nil { - _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quote(table.Name)+"), 1), false);\n") + _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quoter().Quote(table.Name)+"), 1), false);\n") if err != nil { return err } diff --git a/schemas/quote.go b/schemas/quote.go new file mode 100644 index 00000000..e3571e34 --- /dev/null +++ b/schemas/quote.go @@ -0,0 +1,165 @@ +// Copyright 2020 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 + +import ( + "fmt" + "strings" +) + +// Quoter represents two quote characters +type Quoter [2]string + +// CommonQuoter represetns a common quoter +var CommonQuoter = Quoter{"`", "`"} + +func (q Quoter) IsEmpty() bool { + return q[0] == "" && q[1] == "" +} + +func (q Quoter) Quote(s string) string { + var buf strings.Builder + q.QuoteTo(&buf, s) + return buf.String() +} + +func (q Quoter) Replace(sql string, newQuoter Quoter) string { + if q.IsEmpty() { + return sql + } + + if newQuoter.IsEmpty() { + var buf strings.Builder + for i := 0; i < len(sql); i = i + 1 { + if sql[i] != q[0][0] && sql[i] != q[1][0] { + _ = buf.WriteByte(sql[i]) + } + } + return buf.String() + } + + prefix, suffix := newQuoter[0][0], newQuoter[1][0] + var buf strings.Builder + for i, cnt := 0, 0; i < len(sql); i = i + 1 { + if cnt == 0 && sql[i] == q[0][0] { + _ = buf.WriteByte(prefix) + cnt = 1 + } else if cnt == 1 && sql[i] == q[1][0] { + _ = buf.WriteByte(suffix) + cnt = 0 + } else { + _ = buf.WriteByte(sql[i]) + } + } + return buf.String() +} + +func (q Quoter) ReverseQuote(s string) string { + reverseQuoter := Quoter{q[1], q[0]} + return reverseQuoter.Quote(s) +} + +// Trim removes quotes from s +func (q Quoter) Trim(s string) string { + if len(s) < 2 { + return s + } + + if s[0:1] == q[0] { + s = s[1:] + } + if len(s) > 0 && s[len(s)-1:] == q[0] { + return s[:len(s)-1] + } + return s +} + +func TrimSpaceJoin(a []string, sep string) string { + switch len(a) { + case 0: + return "" + case 1: + return a[0] + } + n := len(sep) * (len(a) - 1) + for i := 0; i < len(a); i++ { + n += len(a[i]) + } + + var b strings.Builder + b.Grow(n) + b.WriteString(strings.TrimSpace(a[0])) + for _, s := range a[1:] { + b.WriteString(sep) + b.WriteString(strings.TrimSpace(s)) + } + return b.String() +} + +func (q Quoter) Join(s []string, splitter string) string { + //return fmt.Sprintf("%s%s%s", q[0], TrimSpaceJoin(s, fmt.Sprintf("%s%s%s", q[1], splitter, q[0])), q[1]) + return q.Quote(TrimSpaceJoin(s, fmt.Sprintf("%s%s%s", q[1], splitter, q[0]))) +} + +func (q Quoter) QuoteTo(buf *strings.Builder, value string) { + if q.IsEmpty() { + buf.WriteString(value) + return + } + + prefix, suffix := q[0][0], q[1][0] + lastCh := 0 // 0 prefix, 1 char, 2 suffix + i := 0 + for i < len(value) { + // start of a token; might be already quoted + if value[i] == '.' { + _ = buf.WriteByte('.') + lastCh = 1 + i++ + } else if value[i] == prefix || value[i] == '`' { + // Has quotes; skip/normalize `name` to prefix+name+sufix + var ch byte + if value[i] == prefix { + ch = suffix + } else { + ch = '`' + } + _ = buf.WriteByte(prefix) + i++ + lastCh = 0 + for ; i < len(value) && value[i] != ch && value[i] != ' '; i++ { + _ = buf.WriteByte(value[i]) + lastCh = 1 + } + _ = buf.WriteByte(suffix) + lastCh = 2 + i++ + } else if value[i] == ' ' { + if lastCh != 2 { + _ = buf.WriteByte(suffix) + lastCh = 2 + } + + // a AS b or a b + for ; i < len(value); i++ { + if value[i] != ' ' && value[i-1] == ' ' && (len(value) > i+1 && !strings.EqualFold(value[i:i+2], "AS")) { + break + } + + _ = buf.WriteByte(value[i]) + lastCh = 1 + } + } else { + // Requires quotes + _ = buf.WriteByte(prefix) + for ; i < len(value) && value[i] != '.' && value[i] != ' '; i++ { + _ = buf.WriteByte(value[i]) + lastCh = 1 + } + _ = buf.WriteByte(suffix) + lastCh = 2 + } + } +} diff --git a/engine_test.go b/schemas/quote_test.go similarity index 79% rename from engine_test.go rename to schemas/quote_test.go index 50522f5f..af773c8b 100644 --- a/engine_test.go +++ b/schemas/quote_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package schemas import ( "strings" @@ -12,10 +12,11 @@ import ( ) func TestQuoteTo(t *testing.T) { + var quoter = Quoter{"[", "]"} test := func(t *testing.T, expected string, value string) { buf := &strings.Builder{} - quoteTo(buf, "[]", value) + quoter.QuoteTo(buf, value) assert.EqualValues(t, expected, buf.String()) } @@ -35,7 +36,12 @@ func TestQuoteTo(t *testing.T) { test(t, `["myschema].[mytable"]`, `"myschema.mytable"`) + test(t, "[message_user] AS [sender]", "`message_user` AS `sender`") + + assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ",")) + buf := &strings.Builder{} - quoteTo(buf, "", "noquote") + quoter = Quoter{"", ""} + quoter.QuoteTo(buf, "noquote") assert.EqualValues(t, "noquote", buf.String()) } diff --git a/session_cond_test.go b/session_cond_test.go index 865890d0..30b9f778 100644 --- a/session_cond_test.go +++ b/session_cond_test.go @@ -137,13 +137,13 @@ func TestIn(t *testing.T) { idsStr = idsStr[:len(idsStr)-1] users := make([]Userinfo, 0) - err = testEngine.In("(id)", ids[0], ids[1], ids[2]).Find(&users) + err = testEngine.In("id", ids[0], ids[1], ids[2]).Find(&users) assert.NoError(t, err) fmt.Println(users) assert.EqualValues(t, 3, len(users)) users = make([]Userinfo, 0) - err = testEngine.In("(id)", ids).Find(&users) + err = testEngine.In("id", ids).Find(&users) assert.NoError(t, err) fmt.Println(users) assert.EqualValues(t, 3, len(users)) @@ -161,7 +161,7 @@ func TestIn(t *testing.T) { idsInterface = append(idsInterface, id) } - err = testEngine.Where(department+" = ?", "dev").In("(id)", idsInterface...).Find(&users) + err = testEngine.Where(department+" = ?", "dev").In("id", idsInterface...).Find(&users) assert.NoError(t, err) fmt.Println(users) assert.EqualValues(t, 3, len(users)) @@ -175,11 +175,11 @@ func TestIn(t *testing.T) { dev := testEngine.GetColumnMapper().Obj2Table("Dev") - err = testEngine.In("(id)", 1).In("(id)", 2).In(department, dev).Find(&users) + err = testEngine.In("id", 1).In("id", 2).In(department, dev).Find(&users) assert.NoError(t, err) fmt.Println(users) - cnt, err = testEngine.In("(id)", ids[0]).Update(&Userinfo{Departname: "dev-"}) + cnt, err = testEngine.In("id", ids[0]).Update(&Userinfo{Departname: "dev-"}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) @@ -189,11 +189,11 @@ func TestIn(t *testing.T) { assert.True(t, has) assert.EqualValues(t, "dev-", user.Departname) - cnt, err = testEngine.In("(id)", ids[0]).Update(&Userinfo{Departname: "dev"}) + cnt, err = testEngine.In("id", ids[0]).Update(&Userinfo{Departname: "dev"}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - cnt, err = testEngine.In("(id)", ids[1]).Delete(&Userinfo{}) + cnt, err = testEngine.In("id", ids[1]).Delete(&Userinfo{}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) } diff --git a/session_find_test.go b/session_find_test.go index 991fadf2..8df3bc84 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -77,14 +77,14 @@ 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) if err != nil { t.Error(err) panic(err) } fmt.Println(users) - err = testEngine.Where("(id) > ?", 2).And("(id) < ?", 10).Find(&users) + err = testEngine.Where("id > ?", 2).And("id < ?", 10).Find(&users) if err != nil { t.Error(err) panic(err) @@ -312,12 +312,12 @@ func TestOrderSameMapper(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) fmt.Println(users) users2 := make([]Userinfo, 0) - err = testEngine.Asc("(id)", "Username").Desc("Height").Find(&users2) + err = testEngine.Asc("id", "Username").Desc("Height").Find(&users2) assert.NoError(t, err) fmt.Println(users2) } @@ -790,8 +790,12 @@ func TestFindJoin(t *testing.T) { DeviceId int64 } + type Order struct { + Id int64 + } + assert.NoError(t, prepareEngine()) - assertSync(t, new(SceneItem), new(DeviceUserPrivrels)) + 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"). @@ -802,6 +806,10 @@ func TestFindJoin(t *testing.T) { 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) + assert.NoError(t, err) } func TestJoinFindLimit(t *testing.T) { diff --git a/session_insert_test.go b/session_insert_test.go index 6190d18a..72b89e09 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -201,7 +201,7 @@ func TestInsertDefault(t *testing.T) { _, err = testEngine.Omit(testEngine.GetColumnMapper().Obj2Table("Status")).Insert(&di2) assert.NoError(t, err) - has, err := testEngine.Desc("(id)").Get(di) + has, err := testEngine.Desc("id").Get(di) assert.NoError(t, err) if !has { err = errors.New("error with no data") @@ -247,7 +247,7 @@ func TestInsertDefault2(t *testing.T) { t.Error(err) } - has, err := testEngine.Desc("(id)").Get(di) + has, err := testEngine.Desc("id").Get(di) if err != nil { t.Error(err) } @@ -257,7 +257,7 @@ func TestInsertDefault2(t *testing.T) { panic(err) } - has, err = testEngine.NoAutoCondition().Desc("(id)").Get(&di2) + has, err = testEngine.NoAutoCondition().Desc("id").Get(&di2) if err != nil { t.Error(err) } @@ -330,7 +330,7 @@ func TestInsertCreated(t *testing.T) { t.Fatal(err) } - has, err := testEngine.Desc("(id)").Get(di) + has, err := testEngine.Desc("id").Get(di) if err != nil { t.Fatal(err) } @@ -352,7 +352,7 @@ func TestInsertCreated(t *testing.T) { if err != nil { t.Fatal(err) } - has, err = testEngine.Desc("(id)").Get(di2) + has, err = testEngine.Desc("id").Get(di2) if err != nil { t.Fatal(err) } @@ -374,7 +374,7 @@ func TestInsertCreated(t *testing.T) { if err != nil { t.Fatal(err) } - has, err = testEngine.Desc("(id)").Get(di3) + has, err = testEngine.Desc("id").Get(di3) if err != nil { t.Fatal(err) } @@ -396,7 +396,7 @@ func TestInsertCreated(t *testing.T) { if err != nil { t.Fatal(err) } - has, err = testEngine.Desc("(id)").Get(di4) + has, err = testEngine.Desc("id").Get(di4) if err != nil { t.Fatal(err) } @@ -418,7 +418,7 @@ func TestInsertCreated(t *testing.T) { if err != nil { t.Fatal(err) } - has, err = testEngine.Desc("(id)").Get(di5) + has, err = testEngine.Desc("id").Get(di5) if err != nil { t.Fatal(err) } @@ -442,7 +442,7 @@ func TestInsertCreated(t *testing.T) { t.Fatal(err) } - has, err = testEngine.Desc("(id)").Get(di6) + has, err = testEngine.Desc("id").Get(di6) if err != nil { t.Fatal(err) } @@ -517,7 +517,7 @@ func TestCreatedJsonTime(t *testing.T) { if err != nil { t.Fatal(err) } - has, err := testEngine.Desc("(id)").Get(di5) + has, err := testEngine.Desc("id").Get(di5) if err != nil { t.Fatal(err) } diff --git a/session_tx_test.go b/session_tx_test.go index da3f0f04..1e3dcabf 100644 --- a/session_tx_test.go +++ b/session_tx_test.go @@ -17,15 +17,12 @@ func TestTransaction(t *testing.T) { assert.NoError(t, prepareEngine()) assertSync(t, new(Userinfo)) - counter := func() { - total, err := testEngine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - } - fmt.Printf("----now total %v records\n", total) + counter := func(t *testing.T) { + _, err := testEngine.Count(&Userinfo{}) + assert.NoError(t, err) } - counter() + counter(t) //defer counter() session := testEngine.NewSession() @@ -39,7 +36,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) @@ -119,7 +116,7 @@ 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) diff --git a/session_update_test.go b/session_update_test.go index cb79bad0..2d310aa1 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -137,7 +137,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: @@ -158,7 +158,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: @@ -175,7 +175,7 @@ func TestForUpdate(t *testing.T) { wg.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: @@ -193,7 +193,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 diff --git a/statement.go b/statement.go index 87cab7cc..26c5bd1b 100644 --- a/statement.go +++ b/statement.go @@ -618,7 +618,7 @@ func (statement *Statement) Cols(columns ...string) *Statement { newColumns := statement.colmap2NewColsWithQuote() statement.ColumnStr = strings.Join(newColumns, ", ") - statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1) + statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.dialect.Quoter().Quote("*"), "*", -1) return statement } @@ -765,6 +765,11 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition statement.joinArgs = append(statement.joinArgs, subQueryArgs...) default: tbName := statement.Engine.TableName(tablename, true) + if !isSubQuery(tbName) { + var buf strings.Builder + statement.Engine.QuoteTo(&buf, tbName) + tbName = buf.String() + } fmt.Fprintf(&buf, "%s ON %v", tbName, condition) } diff --git a/statement_exprparam.go b/statement_exprparam.go index fc62e36f..3231f86a 100644 --- a/statement_exprparam.go +++ b/statement_exprparam.go @@ -9,6 +9,7 @@ import ( "strings" "xorm.io/builder" + "xorm.io/xorm/schemas" ) type ErrUnsupportedExprType struct { @@ -40,7 +41,7 @@ func (exprs *exprParams) addParam(colName string, arg interface{}) { func (exprs *exprParams) isColExist(colName string) bool { for _, name := range exprs.colNames { - if strings.EqualFold(trimQuote(name), trimQuote(colName)) { + if strings.EqualFold(schemas.CommonQuoter.Trim(name), schemas.CommonQuoter.Trim(colName)) { return true } } diff --git a/statement_quote.go b/statement_quote.go deleted file mode 100644 index e22e0d14..00000000 --- a/statement_quote.go +++ /dev/null @@ -1,19 +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 xorm - -func trimQuote(s string) string { - if len(s) == 0 { - return s - } - - if s[0] == '`' { - s = s[1:] - } - if len(s) > 0 && s[len(s)-1] == '`' { - return s[:len(s)-1] - } - return s -} From 049a28cfb7d56796b8c3477eb2fbdedfba907542 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 25 Feb 2020 12:22:21 +0000 Subject: [PATCH 029/112] Add leveldb cache store (#1545) Add leveldb cache store Reviewed-on: https://gitea.com/xorm/xorm/pulls/1545 --- caches/encode.go | 58 ++++++++++++ caches/leveldb.go | 94 +++++++++++++++++++ caches/leveldb_test.go | 37 ++++++++ caches/{cache_lru.go => lru.go} | 0 caches/{cache_lru_test.go => lru_test.go} | 0 ...{cache_memory_store.go => memory_store.go} | 0 ...ory_store_test.go => memory_store_test.go} | 0 go.mod | 1 + go.sum | 14 +++ 9 files changed, 204 insertions(+) create mode 100644 caches/encode.go create mode 100644 caches/leveldb.go create mode 100644 caches/leveldb_test.go rename caches/{cache_lru.go => lru.go} (100%) rename caches/{cache_lru_test.go => lru_test.go} (100%) rename caches/{cache_memory_store.go => memory_store.go} (100%) rename caches/{cache_memory_store_test.go => memory_store_test.go} (100%) diff --git a/caches/encode.go b/caches/encode.go new file mode 100644 index 00000000..4ba39924 --- /dev/null +++ b/caches/encode.go @@ -0,0 +1,58 @@ +// Copyright 2020 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 caches + +import ( + "bytes" + "crypto/md5" + "encoding/gob" + "encoding/json" + "fmt" + "io" +) + +// md5 hash string +func Md5(str string) string { + m := md5.New() + io.WriteString(m, str) + return fmt.Sprintf("%x", m.Sum(nil)) +} +func Encode(data interface{}) ([]byte, error) { + //return JsonEncode(data) + return GobEncode(data) +} + +func Decode(data []byte, to interface{}) error { + //return JsonDecode(data, to) + return GobDecode(data, to) +} + +func GobEncode(data interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(&data) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func GobDecode(data []byte, to interface{}) error { + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + return dec.Decode(to) +} + +func JsonEncode(data interface{}) ([]byte, error) { + val, err := json.Marshal(data) + if err != nil { + return nil, err + } + return val, nil +} + +func JsonDecode(data []byte, to interface{}) error { + return json.Unmarshal(data, to) +} diff --git a/caches/leveldb.go b/caches/leveldb.go new file mode 100644 index 00000000..cbaa66b3 --- /dev/null +++ b/caches/leveldb.go @@ -0,0 +1,94 @@ +// Copyright 2020 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 caches + +import ( + "log" + + "github.com/syndtr/goleveldb/leveldb" +) + +// LevelDBStore implements CacheStore provide local machine +type LevelDBStore struct { + store *leveldb.DB + Debug bool + v interface{} +} + +var _ CacheStore = &LevelDBStore{} + +func NewLevelDBStore(dbfile string) *LevelDBStore { + db := &LevelDBStore{} + if h, err := leveldb.OpenFile(dbfile, nil); err != nil { + panic(err) + } else { + db.store = h + } + return db +} + +func (s *LevelDBStore) Put(key string, value interface{}) error { + val, err := Encode(value) + if err != nil { + if s.Debug { + log.Println("[LevelDB]EncodeErr: ", err, "Key:", key) + } + return err + } + err = s.store.Put([]byte(key), val, nil) + if err != nil { + if s.Debug { + log.Println("[LevelDB]PutErr: ", err, "Key:", key) + } + return err + } + if s.Debug { + log.Println("[LevelDB]Put: ", key) + } + return err +} + +func (s *LevelDBStore) Get(key string) (interface{}, error) { + data, err := s.store.Get([]byte(key), nil) + if err != nil { + if s.Debug { + log.Println("[LevelDB]GetErr: ", err, "Key:", key) + } + if err == leveldb.ErrNotFound { + return nil, ErrNotExist + } + return nil, err + } + + err = Decode(data, &s.v) + if err != nil { + if s.Debug { + log.Println("[LevelDB]DecodeErr: ", err, "Key:", key) + } + return nil, err + } + if s.Debug { + log.Println("[LevelDB]Get: ", key, s.v) + } + return s.v, err +} + +func (s *LevelDBStore) Del(key string) error { + err := s.store.Delete([]byte(key), nil) + if err != nil { + if s.Debug { + log.Println("[LevelDB]DelErr: ", err, "Key:", key) + } + return err + } + if s.Debug { + log.Println("[LevelDB]Del: ", key) + } + return err +} + +func (s *LevelDBStore) Close() { + s.store.Close() +} diff --git a/caches/leveldb_test.go b/caches/leveldb_test.go new file mode 100644 index 00000000..4b314afc --- /dev/null +++ b/caches/leveldb_test.go @@ -0,0 +1,37 @@ +// Copyright 2020 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 caches + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLevelDBStore(t *testing.T) { + store := NewLevelDBStore("./level.db") + var kvs = map[string]interface{}{ + "a": "b", + } + for k, v := range kvs { + assert.NoError(t, store.Put(k, v)) + } + + for k, v := range kvs { + val, err := store.Get(k) + assert.NoError(t, err) + assert.EqualValues(t, v, val) + } + + for k := range kvs { + err := store.Del(k) + assert.NoError(t, err) + } + + for k := range kvs { + _, err := store.Get(k) + assert.EqualValues(t, ErrNotExist, err) + } +} diff --git a/caches/cache_lru.go b/caches/lru.go similarity index 100% rename from caches/cache_lru.go rename to caches/lru.go diff --git a/caches/cache_lru_test.go b/caches/lru_test.go similarity index 100% rename from caches/cache_lru_test.go rename to caches/lru_test.go diff --git a/caches/cache_memory_store.go b/caches/memory_store.go similarity index 100% rename from caches/cache_memory_store.go rename to caches/memory_store.go diff --git a/caches/cache_memory_store_test.go b/caches/memory_store_test.go similarity index 100% rename from caches/cache_memory_store_test.go rename to caches/memory_store_test.go diff --git a/go.mod b/go.mod index 07bccdc6..e81ff392 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.10.0 github.com/stretchr/testify v1.4.0 + github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 google.golang.org/appengine v1.6.0 // indirect xorm.io/builder v0.3.6 diff --git a/go.sum b/go.sum index 4666c8ae..42a8bdd3 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,7 @@ github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zA 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/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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -31,7 +32,9 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/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/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= @@ -42,6 +45,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -60,7 +64,9 @@ github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 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/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -83,6 +89,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 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/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -101,20 +109,24 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r 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 h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -135,7 +147,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= From f3d9c49635a599dfb2d82b99795f7d977ff15c9f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 26 Feb 2020 01:05:04 +0000 Subject: [PATCH 030/112] Add changelog file and tool configuration (#1546) Add changelog file and tool configuration Reviewed-on: https://gitea.com/xorm/xorm/pulls/1546 --- .changelog.yml | 57 +++++++++++++++++++++++ CHANGELOG.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 41 ++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 .changelog.yml create mode 100644 CHANGELOG.md diff --git a/.changelog.yml b/.changelog.yml new file mode 100644 index 00000000..4ab1de93 --- /dev/null +++ b/.changelog.yml @@ -0,0 +1,57 @@ +# The full repository name +repo: xorm/xorm + +# Service type (gitea or github) +service: gitea + +# Base URL for Gitea instance if using gitea service type (optional) +# Default: https://gitea.com +base-url: + +# Changelog groups and which labeled PRs to add to each group +groups: + - + name: BREAKING + labels: + - kind/breaking + - + name: FEATURES + labels: + - kind/feature + - + name: SECURITY + labels: + - kind/security + - + name: BUGFIXES + labels: + - kind/bug + - + name: ENHANCEMENTS + labels: + - kind/enhancement + - kind/refactor + - kind/ui + - + name: TESTING + labels: + - kind/testing + - + name: TRANSLATION + labels: + - kind/translation + - + name: BUILD + labels: + - kind/build + - kind/lint + - + name: DOCS + labels: + - kind/docs + - + name: MISC + default: true + +# regex indicating which labels to skip for the changelog +skip-labels: skip-changelog|backport\/.+ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..b5917cf6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,121 @@ +# Changelog + +This changelog goes through all the changes that have been made in each release +without substantial changes to our git log. + +## [1.0.0](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1242) - to be released + +* BUGFIXES + * Fix mssql quote (#1535) + * Fix join table name quote bug (#1534) +* ENHANCEMENTS + * Move reserve words related files into dialects sub package (#1544) + * Merge core package back into the main repository and split into serval sub packages. (#1543) + +## old changelog + +* **v0.6.5** + * Postgres schema support + * vgo support + * Add FindAndCount + * Database special params support via NewEngineWithParams + * Some bugs fixed + +* **v0.6.4** + * Automatical Read/Write seperatelly + * Query/QueryString/QueryInterface and action with Where/And + * Get support non-struct variables + * BufferSize on Iterate + * fix some other bugs. + +* **v0.6.3** + * merge tests to main project + * add `Exist` function + * add `SumInt` function + * Mysql now support read and create column comment. + * fix time related bugs. + * fix some other bugs. + +* **v0.6.2** + * refactor tag parse methods + * add Scan features to Get + * add QueryString method + +* **v0.4.5** + * many bugs fixed + * extends support unlimited deep + * Delete Limit support + +* **v0.4.4** + * ql database expriment support + * tidb database expriment support + * sql.NullString and etc. field support + * select ForUpdate support + * many bugs fixed + +* **v0.4.3** + * Json column type support + * oracle expirement support + * bug fixed + +* **v0.4.2** + * Transaction will auto rollback if not Rollback or Commit be called. + * Gonic Mapper support + * bug fixed + +* **v0.4.1** + * deleted tag support for soft delete + * bug fixed + +* **v0.4.0 RC1** + Changes: + * moved xorm cmd to [github.com/go-xorm/cmd](github.com/go-xorm/cmd) + * refactored general DB operation a core lib at [github.com/go-xorm/core](https://github.com/go-xorm/core) + * moved tests to github.com/go-xorm/tests [github.com/go-xorm/tests](github.com/go-xorm/tests) + + Improvements: + * Prepared statement cache + * Add Incr API + * Specify Timezone Location + +* **v0.3.2** + Improvements: + * Add AllCols & MustCols function + * Add TableName for custom table name + + Bug Fixes: + * #46 + * #51 + * #53 + * #89 + * #86 + * #92 + +* **v0.3.1** + + Features: + * Support MSSQL DB via ODBC driver ([github.com/lunny/godbc](https://github.com/lunny/godbc)); + * Composite Key, using multiple pk xorm tag + * Added Row() API as alternative to Iterate() API for traversing result set, provide similar usages to sql.Rows type + * ORM struct allowed declaration of pointer builtin type as members to allow null DB fields + * Before and After Event processors + + Improvements: + * Allowed int/int32/int64/uint/uint32/uint64/string as Primary Key type + * Performance improvement for Get()/Find()/Iterate() + + +* **v0.2.3** : Improved documents; Optimistic Locking support; Timestamp with time zone support; Mapper change to tableMapper and columnMapper & added PrefixMapper & SuffixMapper support custom table or column name's prefix and suffix;Insert now return affected, err instead of id, err; Added UseBool & Distinct; +* **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handler;Added SetMaxConns(go1.2+) support; some bugs fixed. +* **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md); some bug fixed. +* **v0.2.0** : Added Cache supported, select is speeder up 3~5x; Added SameMapper for same name between struct and table; Added Sync method for auto added tables, columns, indexes; +* **v0.1.9** : Added postgres and mymysql supported; Added ` and ? supported on Raw SQL even if postgres; Added Cols, StoreEngine, Charset function, Added many column data type supported, please see [Mapping Rules](#mapping). +* **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping). +* **v0.1.7** : Added IConnectPool interface and NoneConnectPool, SysConnectPool, SimpleConnectPool the three implements. You can choose one of them and the default is SysConnectPool. You can customrize your own connection pool. struct Engine added Close method, It should be invoked before system exit. +* **v0.1.6** : Added conversion interface support; added struct derive support; added single mapping support +* **v0.1.5** : Added multi threads support; added Sql() function for struct query; Get function changed return inteface; MakeSession and Create are instead with NewSession and NewEngine. +* **v0.1.4** : Added simple cascade load support; added more data type supports. +* **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support +* **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction +* **v0.1.1** : Add Id, In functions and improved README +* **v0.1.0** : Initial release. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 442aa4d3..a6925a5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,47 @@ e.g., // !lunny! this is comments made by lunny ``` +### Build xorm and test it locally + +Once you write some codes on your feature branch, you could build and test locally at first. Just + +``` +make build +``` +and +``` +make test +``` + +The `make test` is an alias of `make test-sqlite`, it will run the tests on a sqlite database file. No extra thing needed to do except you need to cgo compile enviroment. + +If you write a new test method, you could run + +``` +make test-sqlite#TestMyNewMethod +``` + +that will only run the special test method. + +If you want to run another datase, you have to prepare a running database at first, and then, you could + +``` +TEST_MYSQL_HOST= TEST_MYSQL_CHARSET= TEST_MYSQL_DBNAME= TEST_MYSQL_USERNAME= TEST_MYSQL_PASSWORD= make test-mysql +``` + +or other databases: +``` +TEST_MSSQL_HOST= TEST_MSSQL_DBNAME= TEST_MSSQL_USERNAME= TEST_MSSQL_PASSWORD= make test-mssql +``` +``` +TEST_PGSQL_HOST= TEST_PGSQL_SCHEMA= TEST_PGSQL_DBNAME= TEST_PGSQL_USERNAME= TEST_PGSQL_PASSWORD= make test-postgres +``` +``` +TEST_TIDB_HOST= TEST_TIDB_DBNAME= TEST_TIDB_USERNAME= TEST_TIDB_PASSWORD= make test-tidb +``` + +And if your branch is related with cache, you could also enable it via `TEST_CACHE_ENABLE=true`. + ### Patch review Help review existing open [pull requests](https://help.github.com/articles/using-pull-requests) by commenting on the code or From 390effb8a462d494e041cd14faeaac56cdb9d3e9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 26 Feb 2020 12:45:10 +0000 Subject: [PATCH 031/112] Move zero functions to a standalone package (#1548) Remove depreciated functions and move some functions to schemas Move zero functions to a standalone package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1548 --- engine.go | 48 +----------------- engine_cond.go | 3 +- helpers.go | 107 ----------------------------------------- helpers_test.go | 9 ---- internal/utils/zero.go | 98 +++++++++++++++++++++++++++++++++++++ schemas/pk.go | 11 +++++ schemas/quote.go | 21 ++++---- schemas/quote_test.go | 10 ++++ session.go | 2 +- session_cond.go | 15 ------ session_convert.go | 4 +- session_find.go | 6 +-- session_insert.go | 21 ++++---- session_query.go | 4 +- session_update.go | 3 +- statement.go | 18 ++++--- 16 files changed, 167 insertions(+), 213 deletions(-) create mode 100644 internal/utils/zero.go diff --git a/engine.go b/engine.go index b60234e0..1ab2eed2 100644 --- a/engine.go +++ b/engine.go @@ -191,11 +191,6 @@ func (engine *Engine) SupportInsertMany() bool { return engine.dialect.SupportInsertMany() } -func (engine *Engine) quoteColumns(columnStr string) string { - columns := strings.Split(columnStr, ",") - return engine.dialect.Quoter().Join(columns, ",") -} - // Quote Use QuoteStr quote the string sql func (engine *Engine) Quote(value string) string { value = strings.TrimSpace(value) @@ -222,18 +217,6 @@ func (engine *Engine) QuoteTo(buf *strings.Builder, value string) { engine.dialect.Quoter().QuoteTo(buf, value) } -/* -func (engine *Engine) quote(sql string) string { - return engine.dialect.Quote(sql) -}*/ - -// SqlType will be deprecated, please use SQLType instead -// -// Deprecated: use SQLType instead -func (engine *Engine) SqlType(c *schemas.Column) string { - return engine.SQLType(c) -} - // SQLType A simple wrapper to dialect's core.SqlType method func (engine *Engine) SQLType(c *schemas.Column) string { return engine.dialect.SQLType(c) @@ -335,14 +318,6 @@ func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) { } } -// Sql provides raw sql input parameter. When you have a complex SQL statement -// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. -// -// Deprecated: use SQL instead. -func (engine *Engine) Sql(querystring string, args ...interface{}) *Session { - return engine.SQL(querystring, args...) -} - // SQL method let's you manually write raw SQL and operate // For example: // @@ -597,13 +572,6 @@ func (engine *Engine) Where(query interface{}, args ...interface{}) *Session { return session.Where(query, args...) } -// Id will be deprecated, please use ID instead -func (engine *Engine) Id(id interface{}) *Session { - session := engine.NewSession() - session.isAutoClose = true - return session.Id(id) -} - // ID method provoide a condition as (id) = ? func (engine *Engine) ID(id interface{}) *Session { session := engine.NewSession() @@ -1092,23 +1060,9 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { return session.IsTableExist(beanOrTableName) } -// IdOf get id from one struct -// -// Deprecated: use IDOf instead. -func (engine *Engine) IdOf(bean interface{}) schemas.PK { - return engine.IDOf(bean) -} - // IDOf get id from one struct func (engine *Engine) IDOf(bean interface{}) schemas.PK { - return engine.IdOfV(reflect.ValueOf(bean)) -} - -// IdOfV get id from one value of struct -// -// Deprecated: use IDOfV instead. -func (engine *Engine) IdOfV(rv reflect.Value) schemas.PK { - return engine.IDOfV(rv) + return engine.IDOfV(reflect.ValueOf(bean)) } // IDOfV get id from one value of struct diff --git a/engine_cond.go b/engine_cond.go index 480e7ded..949d705d 100644 --- a/engine_cond.go +++ b/engine_cond.go @@ -12,6 +12,7 @@ import ( "time" "xorm.io/builder" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -169,7 +170,7 @@ func (engine *Engine) buildConds(table *schemas.Table, bean interface{}, pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) // fix non-int pk issues //if pkField.Int() != 0 { - if pkField.IsValid() && !isZero(pkField.Interface()) { + if pkField.IsValid() && !utils.IsZero(pkField.Interface()) { val = pkField.Interface() } else { continue diff --git a/helpers.go b/helpers.go index b7c583f7..75393ae3 100644 --- a/helpers.go +++ b/helpers.go @@ -11,8 +11,6 @@ import ( "sort" "strconv" "strings" - - "xorm.io/xorm/schemas" ) // str2PK convert string value to primary key value according to tp @@ -95,95 +93,6 @@ func str2PK(s string, tp reflect.Type) (interface{}, error) { return v.Interface(), nil } -type zeroable interface { - IsZero() bool -} - -func isZero(k interface{}) bool { - switch k.(type) { - case int: - return k.(int) == 0 - case int8: - return k.(int8) == 0 - case int16: - return k.(int16) == 0 - case int32: - return k.(int32) == 0 - case int64: - return k.(int64) == 0 - case uint: - return k.(uint) == 0 - case uint8: - return k.(uint8) == 0 - case uint16: - return k.(uint16) == 0 - case uint32: - return k.(uint32) == 0 - case uint64: - return k.(uint64) == 0 - case float32: - return k.(float32) == 0 - case float64: - return k.(float64) == 0 - case bool: - return k.(bool) == false - case string: - return k.(string) == "" - case zeroable: - return k.(zeroable).IsZero() - } - return false -} - -func isZeroValue(v reflect.Value) bool { - if isZero(v.Interface()) { - return true - } - switch v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return v.IsNil() - } - return false -} - -func isStructZero(v reflect.Value) bool { - if !v.IsValid() { - return true - } - - for i := 0; i < v.NumField(); i++ { - field := v.Field(i) - switch field.Kind() { - case reflect.Ptr: - field = field.Elem() - fallthrough - case reflect.Struct: - if !isStructZero(field) { - return false - } - default: - if field.CanInterface() && !isZero(field.Interface()) { - return false - } - } - } - return true -} - -func isArrayValueZero(v reflect.Value) bool { - if !v.IsValid() || v.Len() == 0 { - return true - } - - for i := 0; i < v.Len(); i++ { - if !isZero(v.Index(i).Interface()) { - return false - } - } - - return true -} - func int64ToIntValue(id int64, tp reflect.Type) reflect.Value { var v interface{} kind := tp.Kind() @@ -229,15 +138,6 @@ func int64ToInt(id int64, tp reflect.Type) interface{} { return int64ToIntValue(id, tp).Interface() } -func isPKZero(pk schemas.PK) bool { - for _, k := range pk { - if isZero(k) { - return true - } - } - return false -} - func indexNoCase(s, sep string) int { return strings.Index(strings.ToLower(s), strings.ToLower(sep)) } @@ -314,10 +214,3 @@ func eraseAny(value string, strToErase ...string) string { return replacer.Replace(value) } - -func quoteColumns(cols []string, quoteFunc func(string) string, sep string) string { - for i := range cols { - cols[i] = quoteFunc(cols[i]) - } - return strings.Join(cols, sep+" ") -} diff --git a/helpers_test.go b/helpers_test.go index caf7b9f0..fc9ece27 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -16,12 +16,3 @@ func TestEraseAny(t *testing.T) { assert.EqualValues(t, "SELECT * FROM table.[table_name]", eraseAny(raw, "`")) assert.EqualValues(t, "SELECT * FROM table.table_name", eraseAny(raw, "`", "[", "]")) } - -func TestQuoteColumns(t *testing.T) { - cols := []string{"f1", "f2", "f3"} - quoteFunc := func(value string) string { - return "[" + value + "]" - } - - assert.EqualValues(t, "[f1], [f2], [f3]", quoteColumns(cols, quoteFunc, ",")) -} diff --git a/internal/utils/zero.go b/internal/utils/zero.go new file mode 100644 index 00000000..8fd1870c --- /dev/null +++ b/internal/utils/zero.go @@ -0,0 +1,98 @@ +// Copyright 2020 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" +) + +type Zeroable interface { + IsZero() bool +} + +func IsZero(k interface{}) bool { + switch k.(type) { + case int: + return k.(int) == 0 + case int8: + return k.(int8) == 0 + case int16: + return k.(int16) == 0 + case int32: + return k.(int32) == 0 + case int64: + return k.(int64) == 0 + case uint: + return k.(uint) == 0 + case uint8: + return k.(uint8) == 0 + case uint16: + return k.(uint16) == 0 + case uint32: + return k.(uint32) == 0 + case uint64: + return k.(uint64) == 0 + case float32: + return k.(float32) == 0 + case float64: + return k.(float64) == 0 + case bool: + return k.(bool) == false + case string: + return k.(string) == "" + case Zeroable: + return k.(Zeroable).IsZero() + } + return false +} + +func IsValueZero(v reflect.Value) bool { + if IsZero(v.Interface()) { + return true + } + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + return false +} + +func IsStructZero(v reflect.Value) bool { + if !v.IsValid() { + return true + } + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + switch field.Kind() { + case reflect.Ptr: + field = field.Elem() + fallthrough + case reflect.Struct: + if !IsStructZero(field) { + return false + } + default: + if field.CanInterface() && !IsZero(field.Interface()) { + return false + } + } + } + return true +} + +func IsArrayZero(v reflect.Value) bool { + if !v.IsValid() || v.Len() == 0 { + return true + } + + for i := 0; i < v.Len(); i++ { + if !IsZero(v.Index(i).Interface()) { + return false + } + } + + return true +} diff --git a/schemas/pk.go b/schemas/pk.go index 3fd3d28b..03916b44 100644 --- a/schemas/pk.go +++ b/schemas/pk.go @@ -7,6 +7,8 @@ package schemas import ( "bytes" "encoding/gob" + + "xorm.io/xorm/internal/utils" ) type PK []interface{} @@ -16,6 +18,15 @@ func NewPK(pks ...interface{}) *PK { return &p } +func (p *PK) IsZero() bool { + for _, k := range *p { + if utils.IsZero(k) { + return true + } + } + return false +} + func (p *PK) ToString() (string, error) { buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) diff --git a/schemas/quote.go b/schemas/quote.go index e3571e34..5dac6d27 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -5,7 +5,6 @@ package schemas import ( - "fmt" "strings" ) @@ -76,7 +75,7 @@ func (q Quoter) Trim(s string) string { return s } -func TrimSpaceJoin(a []string, sep string) string { +func (q Quoter) Join(a []string, sep string) string { switch len(a) { case 0: return "" @@ -90,19 +89,21 @@ func TrimSpaceJoin(a []string, sep string) string { var b strings.Builder b.Grow(n) - b.WriteString(strings.TrimSpace(a[0])) - for _, s := range a[1:] { - b.WriteString(sep) + for i, s := range a { + if i > 0 { + b.WriteString(sep) + } + if q[0] != "" { + b.WriteString(q[0]) + } b.WriteString(strings.TrimSpace(s)) + if q[1] != "" { + b.WriteString(q[1]) + } } return b.String() } -func (q Quoter) Join(s []string, splitter string) string { - //return fmt.Sprintf("%s%s%s", q[0], TrimSpaceJoin(s, fmt.Sprintf("%s%s%s", q[1], splitter, q[0])), q[1]) - return q.Quote(TrimSpaceJoin(s, fmt.Sprintf("%s%s%s", q[1], splitter, q[0]))) -} - func (q Quoter) QuoteTo(buf *strings.Builder, value string) { if q.IsEmpty() { buf.WriteString(value) diff --git a/schemas/quote_test.go b/schemas/quote_test.go index af773c8b..5eea05d3 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -45,3 +45,13 @@ func TestQuoteTo(t *testing.T) { quoter.QuoteTo(buf, "noquote") assert.EqualValues(t, "noquote", buf.String()) } + +func TestJoin(t *testing.T) { + cols := []string{"f1", "f2", "f3"} + quoter := Quoter{"[", "]"} + + assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", ")) + + quoter = Quoter{"", ""} + assert.EqualValues(t, "f1, f2, f3", quoter.Join(cols, ", ")) +} diff --git a/session.go b/session.go index 57e4055b..3e31d3d7 100644 --- a/session.go +++ b/session.go @@ -703,7 +703,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b return nil, err } - if !isPKZero(pk) { + 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 diff --git a/session_cond.go b/session_cond.go index b16bdea8..72e3abc3 100644 --- a/session_cond.go +++ b/session_cond.go @@ -6,14 +6,6 @@ package xorm import "xorm.io/builder" -// Sql provides raw sql input parameter. When you have a complex SQL statement -// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. -// -// Deprecated: use SQL instead. -func (session *Session) Sql(query string, args ...interface{}) *Session { - return session.SQL(query, args...) -} - // SQL provides raw sql input parameter. When you have a complex SQL statement // and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. func (session *Session) SQL(query interface{}, args ...interface{}) *Session { @@ -39,13 +31,6 @@ func (session *Session) Or(query interface{}, args ...interface{}) *Session { return session } -// Id provides converting id as a query condition -// -// Deprecated: use ID instead -func (session *Session) Id(id interface{}) *Session { - return session.ID(id) -} - // ID provides converting id as a query condition func (session *Session) ID(id interface{}) *Session { session.statement.ID(id) diff --git a/session_convert.go b/session_convert.go index 24c51011..020507ed 100644 --- a/session_convert.go +++ b/session_convert.go @@ -224,7 +224,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val return err } - if !isPKZero(pk) { + 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 @@ -506,7 +506,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val return err } - if !isPKZero(pk) { + 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 diff --git a/session_find.go b/session_find.go index 7ae54a5f..566e83dd 100644 --- a/session_find.go +++ b/session_find.go @@ -142,7 +142,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) if session.statement.JoinStr == "" { if columnStr == "" { if session.statement.GroupByStr != "" { - columnStr = session.engine.quoteColumns(session.statement.GroupByStr) + columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) } else { columnStr = session.statement.genColumnStr() } @@ -150,7 +150,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } else { if columnStr == "" { if session.statement.GroupByStr != "" { - columnStr = session.engine.quoteColumns(session.statement.GroupByStr) + columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) } else { columnStr = "*" } @@ -417,7 +417,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } else { session.engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean) - pk := session.engine.IdOf(bean) + pk := session.engine.IDOf(bean) xid, err := pk.ToString() if err != nil { return err diff --git a/session_insert.go b/session_insert.go index 4a026b78..b788e629 100644 --- a/session_insert.go +++ b/session_insert.go @@ -13,6 +13,7 @@ import ( "strings" "xorm.io/builder" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -153,7 +154,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error return 0, err } fieldValue := *ptrFieldValue - if col.IsAutoIncrement && isZero(fieldValue.Interface()) { + if col.IsAutoIncrement && utils.IsZero(fieldValue.Interface()) { continue } if col.MapType == schemas.ONLYFROMDB { @@ -204,7 +205,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error } fieldValue := *ptrFieldValue - if col.IsAutoIncrement && isZero(fieldValue.Interface()) { + if col.IsAutoIncrement && utils.IsZero(fieldValue.Interface()) { continue } if col.MapType == schemas.ONLYFROMDB { @@ -250,19 +251,21 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error } cleanupProcessorsClosures(&session.beforeClosures) + quoter := session.engine.dialect.Quoter() var sql string + colStr := quoter.Join(colNames, ",") if session.engine.dialect.DBType() == schemas.ORACLE { temp := fmt.Sprintf(") INTO %s (%v) VALUES (", - session.engine.Quote(tableName), - quoteColumns(colNames, session.engine.Quote, ",")) + quoter.Quote(tableName), + colStr) sql = fmt.Sprintf("INSERT ALL INTO %s (%v) VALUES (%v) SELECT 1 FROM DUAL", - session.engine.Quote(tableName), - quoteColumns(colNames, session.engine.Quote, ","), + quoter.Quote(tableName), + colStr, strings.Join(colMultiPlaces, temp)) } else { sql = fmt.Sprintf("INSERT INTO %s (%v) VALUES (%v)", - session.engine.Quote(tableName), - quoteColumns(colNames, session.engine.Quote, ","), + quoter.Quote(tableName), + colStr, strings.Join(colMultiPlaces, "),(")) } res, err := session.exec(sql, args...) @@ -679,7 +682,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac // !evalphobia! set fieldValue as nil when column is nullable and zero-value if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok { - if col.Nullable && isZeroValue(fieldValue) { + if col.Nullable && utils.IsValueZero(fieldValue) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) } diff --git a/session_query.go b/session_query.go index 0623c90c..afed4bcb 100644 --- a/session_query.go +++ b/session_query.go @@ -36,7 +36,7 @@ func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interfa if session.statement.JoinStr == "" { if columnStr == "" { if session.statement.GroupByStr != "" { - columnStr = session.engine.quoteColumns(session.statement.GroupByStr) + columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) } else { columnStr = session.statement.genColumnStr() } @@ -44,7 +44,7 @@ func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interfa } else { if columnStr == "" { if session.statement.GroupByStr != "" { - columnStr = session.engine.quoteColumns(session.statement.GroupByStr) + columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) } else { columnStr = "*" } diff --git a/session_update.go b/session_update.go index 7316c3b2..427d452d 100644 --- a/session_update.go +++ b/session_update.go @@ -13,6 +13,7 @@ import ( "xorm.io/builder" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -518,7 +519,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac // !evalphobia! set fieldValue as nil when column is nullable and zero-value if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok { - if col.Nullable && isZeroValue(fieldValue) { + if col.Nullable && utils.IsValueZero(fieldValue) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) } diff --git a/statement.go b/statement.go index 26c5bd1b..9dc5bf52 100644 --- a/statement.go +++ b/statement.go @@ -13,6 +13,7 @@ import ( "xorm.io/builder" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -304,7 +305,7 @@ func (statement *Statement) buildUpdates(bean interface{}, // !evalphobia! set fieldValue as nil when column is nullable and zero-value if b, ok := getFlagForColumn(nullableMap, col); ok { - if b && col.Nullable && isZero(fieldValue.Interface()) { + if b && col.Nullable && utils.IsZero(fieldValue.Interface()) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) fieldType = reflect.TypeOf(fieldValue.Interface()) @@ -404,7 +405,7 @@ func (statement *Statement) buildUpdates(bean interface{}, if len(table.PrimaryKeys) == 1 { pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) // fix non-int pk issues - if pkField.IsValid() && (!requiredField && !isZero(pkField.Interface())) { + if pkField.IsValid() && (!requiredField && !utils.IsZero(pkField.Interface())) { val = pkField.Interface() } else { continue @@ -418,7 +419,7 @@ func (statement *Statement) buildUpdates(bean interface{}, } } else { // Blank struct could not be as update data - if requiredField || !isStructZero(fieldValue) { + if requiredField || !utils.IsStructZero(fieldValue) { bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { panic(fmt.Sprintf("mashal %v failed", fieldValue.Interface())) @@ -439,7 +440,7 @@ func (statement *Statement) buildUpdates(bean interface{}, continue } if fieldType.Kind() == reflect.Array { - if isArrayValueZero(fieldValue) { + if utils.IsArrayZero(fieldValue) { continue } } else if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 { @@ -939,6 +940,11 @@ func (statement *Statement) genConds(bean interface{}) (string, []interface{}, e return builder.ToSQL(statement.cond) } +func (statement *Statement) quoteColumnStr(columnStr string) string { + columns := strings.Split(columnStr, ",") + return statement.Engine.dialect.Quoter().Join(columns, ",") +} + func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, error) { v := rValue(bean) isStruct := v.Kind() == reflect.Struct @@ -954,7 +960,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, if len(statement.JoinStr) == 0 { if len(columnStr) == 0 { if len(statement.GroupByStr) > 0 { - columnStr = statement.Engine.quoteColumns(statement.GroupByStr) + columnStr = statement.quoteColumnStr(statement.GroupByStr) } else { columnStr = statement.genColumnStr() } @@ -962,7 +968,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, } else { if len(columnStr) == 0 { if len(statement.GroupByStr) > 0 { - columnStr = statement.Engine.quoteColumns(statement.GroupByStr) + columnStr = statement.quoteColumnStr(statement.GroupByStr) } } } From 757db3b8886ed113bbd7d19be8644b9972005285 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 26 Feb 2020 23:46:17 +0000 Subject: [PATCH 032/112] Moved examples out of the main repository (#1550) Moved examples out of the main repository Reviewed-on: https://gitea.com/xorm/xorm/pulls/1550 --- examples/README.md | 5 - examples/cache/cache.go | 110 ------------------- examples/cache_gorountine/cache_goroutine.go | 109 ------------------ examples/conversion/conversion.go | 81 -------------- examples/derive/derive.go | 70 ------------ examples/find/find.go | 51 --------- examples/goroutine/goroutine.go | 108 ------------------ examples/mapping/single_mapping.go | 57 ---------- examples/max_connect/max_connect.go | 108 ------------------ examples/sync/sync.go | 106 ------------------ examples/tables/tables.go | 34 ------ 11 files changed, 839 deletions(-) delete mode 100644 examples/README.md delete mode 100644 examples/cache/cache.go delete mode 100644 examples/cache_gorountine/cache_goroutine.go delete mode 100644 examples/conversion/conversion.go delete mode 100644 examples/derive/derive.go delete mode 100644 examples/find/find.go delete mode 100644 examples/goroutine/goroutine.go delete mode 100644 examples/mapping/single_mapping.go delete mode 100644 examples/max_connect/max_connect.go delete mode 100644 examples/sync/sync.go delete mode 100644 examples/tables/tables.go diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 666c6cf9..00000000 --- a/examples/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Xorm Examples - -Notice: all the examples will ask you install extra package `github.com/mattn/go-sqlite3`, since it depends on cgo. You have to compile it after you install a c++ compile. Please see [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3). - -And then, you can run the examples via `go run xxx.go`. Every go file is a standalone example. diff --git a/examples/cache/cache.go b/examples/cache/cache.go deleted file mode 100644 index 4ac8eedf..00000000 --- a/examples/cache/cache.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "fmt" - "os" - - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" - "xorm.io/xorm/caches" -) - -// User describes a user -type User struct { - Id int64 - Name string -} - -func main() { - f := "caches.db" - os.Remove(f) - - Orm, err := xorm.NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL(true) - cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 1000) - Orm.SetDefaultCacher(cacher) - - err = Orm.CreateTables(&User{}) - if err != nil { - fmt.Println(err) - return - } - - _, err = Orm.Insert(&User{Name: "xlw"}) - if err != nil { - fmt.Println(err) - return - } - - var users []User - err = Orm.Find(&users) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println("users:", users) - - var users2 []User - err = Orm.Find(&users2) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println("users2:", users2) - - var users3 []User - err = Orm.Find(&users3) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println("users3:", users3) - - user4 := new(User) - has, err := Orm.ID(1).Get(user4) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println("user4:", has, user4) - - user4.Name = "xiaolunwen" - _, err = Orm.ID(1).Update(user4) - if err != nil { - fmt.Println(err) - return - } - fmt.Println("user4:", user4) - - user5 := new(User) - has, err = Orm.ID(1).Get(user5) - if err != nil { - fmt.Println(err) - return - } - fmt.Println("user5:", has, user5) - - _, err = Orm.ID(1).Delete(new(User)) - if err != nil { - fmt.Println(err) - return - } - - for { - user6 := new(User) - has, err = Orm.ID(1).Get(user6) - if err != nil { - fmt.Println(err) - return - } - fmt.Println("user6:", has, user6) - } -} diff --git a/examples/cache_gorountine/cache_goroutine.go b/examples/cache_gorountine/cache_goroutine.go deleted file mode 100644 index 3543cba8..00000000 --- a/examples/cache_gorountine/cache_goroutine.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - _ "github.com/go-sql-driver/mysql" - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" - "xorm.io/xorm/caches" -) - -// User describes a user -type User struct { - Id int64 - Name string -} - -func sqliteEngine() (*xorm.Engine, error) { - os.Remove("./test.db") - return xorm.NewEngine("sqlite3", "./goroutine.db") -} - -func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") -} - -var u = &User{} - -func test(engine *xorm.Engine) { - err := engine.CreateTables(u) - if err != nil { - fmt.Println(err) - return - } - - size := 500 - queue := make(chan int, size) - - for i := 0; i < size; i++ { - go func(x int) { - //x := i - err := engine.Ping() - if err != nil { - fmt.Println(err) - } else { - for j := 0; j < 10; j++ { - if x+j < 2 { - _, err = engine.Get(u) - } else if x+j < 4 { - users := make([]User, 0) - err = engine.Find(&users) - } else if x+j < 8 { - _, err = engine.Count(u) - } else if x+j < 16 { - _, err = engine.Insert(&User{Name: "xlw"}) - } else if x+j < 32 { - //_, err = engine.ID(1).Delete(u) - _, err = engine.Delete(u) - } - if err != nil { - fmt.Println(err) - queue <- x - return - } - } - fmt.Printf("%v success!\n", x) - } - queue <- x - }(i) - } - - for i := 0; i < size; i++ { - <-queue - } - - //conns := atomic.LoadInt32(&xorm.ConnectionNum) - //fmt.Println("connection number:", conns) - fmt.Println("end") -} - -func main() { - fmt.Println("-----start sqlite go routines-----") - engine, err := sqliteEngine() - if err != nil { - fmt.Println(err) - return - } - engine.ShowSQL(true) - cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000) - engine.SetDefaultCacher(cacher) - fmt.Println(engine) - test(engine) - fmt.Println("test end") - engine.Close() - - fmt.Println("-----start mysql go routines-----") - engine, err = mysqlEngine() - engine.ShowSQL(true) - cacher = caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000) - engine.SetDefaultCacher(cacher) - if err != nil { - fmt.Println(err) - return - } - defer engine.Close() - test(engine) -} diff --git a/examples/conversion/conversion.go b/examples/conversion/conversion.go deleted file mode 100644 index 9b16fdc1..00000000 --- a/examples/conversion/conversion.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "os" - - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// Status describes a status -type Status struct { - Name string - Color string -} - -// defines some statuses -var ( - Registered = Status{"Registered", "white"} - Approved = Status{"Approved", "green"} - Removed = Status{"Removed", "red"} - Statuses = map[string]Status{ - Registered.Name: Registered, - Approved.Name: Approved, - Removed.Name: Removed, - } -) - -// FromDB implemented xorm.Conversion convent database data to self -func (s *Status) FromDB(bytes []byte) error { - if r, ok := Statuses[string(bytes)]; ok { - *s = r - return nil - } - return errors.New("no this data") -} - -// ToDB implemented xorm.Conversion convent to database data -func (s *Status) ToDB() ([]byte, error) { - return []byte(s.Name), nil -} - -// User describes a user -type User struct { - Id int64 - Name string - Status Status `xorm:"varchar(40)"` -} - -func main() { - f := "conversion.db" - os.Remove(f) - - Orm, err := xorm.NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL(true) - err = Orm.CreateTables(&User{}) - if err != nil { - fmt.Println(err) - return - } - - _, err = Orm.Insert(&User{1, "xlw", Registered}) - if err != nil { - fmt.Println(err) - return - } - - users := make([]User, 0) - err = Orm.Find(&users) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(users) -} diff --git a/examples/derive/derive.go b/examples/derive/derive.go deleted file mode 100644 index ec6fe093..00000000 --- a/examples/derive/derive.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "fmt" - "os" - - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// User describes a user -type User struct { - Id int64 - Name string -} - -// LoginInfo describes a login information -type LoginInfo struct { - Id int64 - IP string - UserId int64 -} - -// LoginInfo1 describes a login information -type LoginInfo1 struct { - LoginInfo `xorm:"extends"` - UserName string -} - -func main() { - f := "derive.db" - os.Remove(f) - - orm, err := xorm.NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - defer orm.Close() - orm.ShowSQL(true) - err = orm.CreateTables(&User{}, &LoginInfo{}) - if err != nil { - fmt.Println(err) - return - } - - _, err = orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1}) - if err != nil { - fmt.Println(err) - return - } - - info := LoginInfo{} - _, err = orm.ID(1).Get(&info) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(info) - - infos := make([]LoginInfo1, 0) - err = orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from - login_info limit 10`).Find(&infos) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(infos) -} diff --git a/examples/find/find.go b/examples/find/find.go deleted file mode 100644 index 9414650e..00000000 --- a/examples/find/find.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// User describes a user -type User struct { - Id int64 - Name string - Created time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` -} - -func main() { - f := "conversion.db" - os.Remove(f) - - orm, err := xorm.NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - orm.ShowSQL(true) - - err = orm.CreateTables(&User{}) - if err != nil { - fmt.Println(err) - return - } - - _, err = orm.Insert(&User{Id: 1, Name: "xlw"}) - if err != nil { - fmt.Println(err) - return - } - - users := make([]User, 0) - err = orm.Find(&users) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(users) -} diff --git a/examples/goroutine/goroutine.go b/examples/goroutine/goroutine.go deleted file mode 100644 index 57cb20a4..00000000 --- a/examples/goroutine/goroutine.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "fmt" - "os" - "runtime" - - _ "github.com/go-sql-driver/mysql" - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// User describes a user -type User struct { - Id int64 - Name string -} - -func sqliteEngine() (*xorm.Engine, error) { - os.Remove("./test.db") - return xorm.NewEngine("sqlite3", "./goroutine.db") -} - -func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") -} - -var u = &User{} - -func test(engine *xorm.Engine) { - err := engine.CreateTables(u) - if err != nil { - fmt.Println(err) - return - } - - size := 100 - queue := make(chan int, size) - - for i := 0; i < size; i++ { - go func(x int) { - //x := i - err := engine.Ping() - if err != nil { - fmt.Println(err) - } else { - /*err = engine.(u) - if err != nil { - fmt.Println("Map user failed") - } else {*/ - for j := 0; j < 10; j++ { - if x+j < 2 { - _, err = engine.Get(u) - } else if x+j < 4 { - users := make([]User, 0) - err = engine.Find(&users) - } else if x+j < 8 { - _, err = engine.Count(u) - } else if x+j < 16 { - _, err = engine.Insert(&User{Name: "xlw"}) - } else if x+j < 32 { - _, err = engine.ID(1).Delete(u) - } - if err != nil { - fmt.Println(err) - queue <- x - return - } - } - fmt.Printf("%v success!\n", x) - //} - } - queue <- x - }(i) - } - - for i := 0; i < size; i++ { - <-queue - } - - //conns := atomic.LoadInt32(&xorm.ConnectionNum) - //fmt.Println("connection number:", conns) - fmt.Println("end") -} - -func main() { - runtime.GOMAXPROCS(2) - fmt.Println("-----start sqlite go routines-----") - engine, err := sqliteEngine() - if err != nil { - fmt.Println(err) - return - } - engine.ShowSQL(true) - fmt.Println(engine) - test(engine) - fmt.Println("test end") - engine.Close() - - fmt.Println("-----start mysql go routines-----") - engine, err = mysqlEngine() - if err != nil { - fmt.Println(err) - return - } - defer engine.Close() - test(engine) -} diff --git a/examples/mapping/single_mapping.go b/examples/mapping/single_mapping.go deleted file mode 100644 index 28a776d0..00000000 --- a/examples/mapping/single_mapping.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "os" - - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// User describes a user -type User struct { - Id int64 - Name string -} - -// LoginInfo describes a login information -type LoginInfo struct { - Id int64 - IP string - UserId int64 - // timestamp should be updated by database, so only allow get from db - TimeStamp string `xorm:"<-"` - // assume - Nonuse int `xorm:"->"` -} - -func main() { - f := "singleMapping.db" - os.Remove(f) - - orm, err := xorm.NewEngine("sqlite3", f) - if err != nil { - fmt.Println(err) - return - } - orm.ShowSQL(true) - err = orm.CreateTables(&User{}, &LoginInfo{}) - if err != nil { - fmt.Println(err) - return - } - - _, err = orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23}) - if err != nil { - fmt.Println(err) - return - } - - info := LoginInfo{} - _, err = orm.ID(1).Get(&info) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(info) -} diff --git a/examples/max_connect/max_connect.go b/examples/max_connect/max_connect.go deleted file mode 100644 index 42e9ce6d..00000000 --- a/examples/max_connect/max_connect.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "fmt" - "os" - "runtime" - - _ "github.com/go-sql-driver/mysql" - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// User describes a user -type User struct { - Id int64 - Name string -} - -func sqliteEngine() (*xorm.Engine, error) { - os.Remove("./test.db") - return xorm.NewEngine("sqlite3", "./goroutine.db") -} - -func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") -} - -var u = &User{} - -func test(engine *xorm.Engine) { - err := engine.CreateTables(u) - if err != nil { - fmt.Println(err) - return - } - - engine.ShowSQL(true) - engine.SetMaxOpenConns(5) - - size := 1000 - queue := make(chan int, size) - - for i := 0; i < size; i++ { - go func(x int) { - //x := i - err := engine.Ping() - if err != nil { - fmt.Println(err) - } else { - /*err = engine.Map(u) - if err != nil { - fmt.Println("Map user failed") - } else {*/ - for j := 0; j < 10; j++ { - if x+j < 2 { - _, err = engine.Get(u) - } else if x+j < 4 { - users := make([]User, 0) - err = engine.Find(&users) - } else if x+j < 8 { - _, err = engine.Count(u) - } else if x+j < 16 { - _, err = engine.Insert(&User{Name: "xlw"}) - } else if x+j < 32 { - _, err = engine.ID(1).Delete(u) - } - if err != nil { - fmt.Println(err) - queue <- x - return - } - } - fmt.Printf("%v success!\n", x) - //} - } - queue <- x - }(i) - } - - for i := 0; i < size; i++ { - <-queue - } - - fmt.Println("end") -} - -func main() { - runtime.GOMAXPROCS(2) - fmt.Println("create engine") - engine, err := sqliteEngine() - if err != nil { - fmt.Println(err) - return - } - engine.ShowSQL(true) - fmt.Println(engine) - test(engine) - fmt.Println("------------------------") - engine.Close() - - engine, err = mysqlEngine() - if err != nil { - fmt.Println(err) - return - } - defer engine.Close() - test(engine) -} diff --git a/examples/sync/sync.go b/examples/sync/sync.go deleted file mode 100644 index 96aa8ae7..00000000 --- a/examples/sync/sync.go +++ /dev/null @@ -1,106 +0,0 @@ -package main - -import ( - "fmt" - - _ "github.com/go-sql-driver/mysql" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -// SyncUser2 describes a user -type SyncUser2 struct { - Id int64 - Name string `xorm:"unique"` - Age int `xorm:"index"` - Title string - Address string - Genre string - Area string - Date int -} - -// SyncLoginInfo2 describes a login information -type SyncLoginInfo2 struct { - Id int64 - IP string `xorm:"index"` - UserId int64 - AddedCol int - // timestamp should be updated by database, so only allow get from db - TimeStamp string - // assume - Nonuse int `xorm:"unique"` - Newa string `xorm:"index"` -} - -func sync(engine *xorm.Engine) error { - return engine.Sync(&SyncLoginInfo2{}, &SyncUser2{}) -} - -func sqliteEngine() (*xorm.Engine, error) { - f := "sync.db" - //os.Remove(f) - - return xorm.NewEngine("sqlite3", f) -} - -func mysqlEngine() (*xorm.Engine, error) { - return xorm.NewEngine("mysql", "root:@/test?charset=utf8") -} - -func postgresEngine() (*xorm.Engine, error) { - return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable") -} - -type engineFunc func() (*xorm.Engine, error) - -func main() { - //engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine} - //engines := []engineFunc{sqliteEngine} - //engines := []engineFunc{mysqlEngine} - engines := []engineFunc{postgresEngine} - for _, enginefunc := range engines { - Orm, err := enginefunc() - fmt.Println("--------", Orm.DriverName(), "----------") - if err != nil { - fmt.Println(err) - return - } - Orm.ShowSQL(true) - err = sync(Orm) - if err != nil { - fmt.Println(err) - } - - _, err = Orm.Where("id > 0").Delete(&SyncUser2{}) - if err != nil { - fmt.Println(err) - } - - user := &SyncUser2{ - Name: "testsdf", - Age: 15, - Title: "newsfds", - Address: "fasfdsafdsaf", - Genre: "fsafd", - Area: "fafdsafd", - Date: 1000, - } - _, err = Orm.Insert(user) - if err != nil { - fmt.Println(err) - return - } - - isexist, err := Orm.IsTableExist("sync_user2") - if err != nil { - fmt.Println(err) - return - } - if !isexist { - fmt.Println("sync_user2 is not exist") - return - } - } -} diff --git a/examples/tables/tables.go b/examples/tables/tables.go deleted file mode 100644 index eb61c795..00000000 --- a/examples/tables/tables.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "fmt" - "os" - - _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm" -) - -func main() { - if len(os.Args) < 2 { - fmt.Println("need db path") - return - } - - orm, err := xorm.NewEngine("sqlite3", os.Args[1]) - if err != nil { - fmt.Println(err) - return - } - defer orm.Close() - orm.ShowSQL(true) - - tables, err := orm.DBMetas() - if err != nil { - fmt.Println(err) - return - } - - for _, table := range tables { - fmt.Println(table.Name) - } -} From 5a5375a170ed53edca5ec80fb7ac731a67e44899 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 00:34:16 +0000 Subject: [PATCH 033/112] Improve statement (#1549) Fix cache bug Improve statement Reviewed-on: https://gitea.com/xorm/xorm/pulls/1549 --- log/logger.go | 8 ++--- schemas/quote.go | 12 +++++-- schemas/quote_test.go | 8 +++++ session.go | 5 +-- session_exist.go | 2 +- session_get.go | 12 +++---- session_update.go | 10 +++--- statement.go | 79 ++++++++++++++++++++++--------------------- statement_test.go | 25 ++++++-------- 9 files changed, 89 insertions(+), 72 deletions(-) diff --git a/log/logger.go b/log/logger.go index b5ab9019..eeb63693 100644 --- a/log/logger.go +++ b/log/logger.go @@ -130,7 +130,7 @@ 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.Sprint(v...)) + s.ERR.Output(2, fmt.Sprintln(v...)) } return } @@ -146,7 +146,7 @@ func (s *SimpleLogger) Errorf(format string, v ...interface{}) { // Debug implement ILogger func (s *SimpleLogger) Debug(v ...interface{}) { if s.level <= LOG_DEBUG { - s.DEBUG.Output(2, fmt.Sprint(v...)) + s.DEBUG.Output(2, fmt.Sprintln(v...)) } return } @@ -162,7 +162,7 @@ func (s *SimpleLogger) Debugf(format string, v ...interface{}) { // Info implement ILogger func (s *SimpleLogger) Info(v ...interface{}) { if s.level <= LOG_INFO { - s.INFO.Output(2, fmt.Sprint(v...)) + s.INFO.Output(2, fmt.Sprintln(v...)) } return } @@ -178,7 +178,7 @@ func (s *SimpleLogger) Infof(format string, v ...interface{}) { // Warn implement ILogger func (s *SimpleLogger) Warn(v ...interface{}) { if s.level <= LOG_WARNING { - s.WARN.Output(2, fmt.Sprint(v...)) + s.WARN.Output(2, fmt.Sprintln(v...)) } return } diff --git a/schemas/quote.go b/schemas/quote.go index 5dac6d27..21327eb0 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -93,17 +93,25 @@ func (q Quoter) Join(a []string, sep string) string { if i > 0 { b.WriteString(sep) } - if q[0] != "" { + if q[0] != "" && s != "*" { b.WriteString(q[0]) } b.WriteString(strings.TrimSpace(s)) - if q[1] != "" { + if q[1] != "" && s != "*" { b.WriteString(q[1]) } } return b.String() } +func (q Quoter) Strings(s []string) []string { + var res = make([]string, 0, len(s)) + for _, a := range s { + res = append(res, q.Quote(a)) + } + return res +} + func (q Quoter) QuoteTo(buf *strings.Builder, value string) { if q.IsEmpty() { buf.WriteString(value) diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 5eea05d3..0c87d3a8 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -55,3 +55,11 @@ func TestJoin(t *testing.T) { quoter = Quoter{"", ""} assert.EqualValues(t, "f1, f2, f3", quoter.Join(cols, ", ")) } + +func TestStrings(t *testing.T) { + cols := []string{"f1", "f2", "t3.f3"} + quoter := Quoter{"[", "]"} + + quotedCols := quoter.Strings(cols) + assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols) +} diff --git a/session.go b/session.go index 3e31d3d7..8c692879 100644 --- a/session.go +++ b/session.go @@ -72,7 +72,8 @@ func (session *Session) Clone() *Session { // Init reset the session as the init status. func (session *Session) Init() { - session.statement.Init() + session.statement.Reset() + session.statement.dialect = session.engine.dialect session.statement.Engine = session.engine session.showSQL = session.engine.showSQL session.isAutoCommit = true @@ -128,7 +129,7 @@ func (session *Session) IsClosed() bool { func (session *Session) resetStatement() { if session.autoResetStatement { - session.statement.Init() + session.statement.Reset() } } diff --git a/session_exist.go b/session_exist.go index 153bb219..d5b0c1d8 100644 --- a/session_exist.go +++ b/session_exist.go @@ -34,7 +34,7 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { return false, ErrTableNotFound } - tableName = session.statement.Engine.Quote(tableName) + tableName = session.statement.quote(tableName) if len(session.statement.JoinStr) > 0 { joinStr = session.statement.JoinStr } diff --git a/session_get.go b/session_get.go index 376ac2c1..c42361a8 100644 --- a/session_get.go +++ b/session_get.go @@ -282,7 +282,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf tableName := session.statement.TableName() cacher := session.engine.getCacher(tableName) - session.engine.logger.Debug("[cacheGet] find sql:", newsql, args) + session.engine.logger.Debug("[cache] Get SQL:", newsql, args) table := session.statement.RefTable ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { @@ -318,19 +318,19 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } ids = []schemas.PK{pk} - session.engine.logger.Debug("[cacheGet] cache ids:", newsql, ids) + session.engine.logger.Debug("[cache] cache ids:", newsql, ids) err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return false, err } } else { - session.engine.logger.Debug("[cacheGet] cache hit sql:", newsql, ids) + session.engine.logger.Debug("[cache] cache hit:", newsql, ids) } if len(ids) > 0 { structValue := reflect.Indirect(reflect.ValueOf(bean)) id := ids[0] - session.engine.logger.Debug("[cacheGet] get bean:", tableName, id) + session.engine.logger.Debug("[cache] get bean:", tableName, id) sid, err := id.ToString() if err != nil { return false, err @@ -343,10 +343,10 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return has, err } - session.engine.logger.Debug("[cacheGet] cache bean:", tableName, id, cacheBean) + session.engine.logger.Debug("[cache] cache bean:", tableName, id, cacheBean) cacher.PutBean(tableName, sid, cacheBean) } else { - session.engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean) + session.engine.logger.Debug("[cache] cache hit:", tableName, id, cacheBean) has = true } structValue.Set(reflect.Indirect(reflect.ValueOf(cacheBean))) diff --git a/session_update.go b/session_update.go index 427d452d..74b180d5 100644 --- a/session_update.go +++ b/session_update.go @@ -341,9 +341,9 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var top string if st.LimitN != nil { limitValue := *st.LimitN - if st.Engine.dialect.DBType() == schemas.MYSQL { + if st.dialect.DBType() == schemas.MYSQL { condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) - } else if st.Engine.dialect.DBType() == schemas.SQLITE { + } else if st.dialect.DBType() == schemas.SQLITE { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -354,7 +354,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.Engine.dialect.DBType() == schemas.POSTGRES { + } else if st.dialect.DBType() == schemas.POSTGRES { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -366,8 +366,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.Engine.dialect.DBType() == schemas.MSSQL { - if st.OrderStr != "" && st.Engine.dialect.DBType() == schemas.MSSQL && + } else if st.dialect.DBType() == schemas.MSSQL { + if st.OrderStr != "" && st.dialect.DBType() == schemas.MSSQL && 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], diff --git a/statement.go b/statement.go index 9dc5bf52..fd6b3962 100644 --- a/statement.go +++ b/statement.go @@ -20,6 +20,7 @@ import ( // Statement save all the sql info for executing SQL type Statement struct { RefTable *schemas.Table + dialect dialects.Dialect Engine *Engine Start int LimitN *int @@ -32,7 +33,6 @@ type Statement struct { ColumnStr string selectStr string useAllCols bool - OmitStr string AltTableName string tableName string RawSQL string @@ -63,8 +63,20 @@ type Statement struct { lastError error } +func newStatement(dialect dialects.Dialect) *Statement { + statement := &Statement{ + dialect: dialect, + } + statement.Reset() + return statement +} + +func (statement *Statement) omitStr() string { + return statement.dialect.Quoter().Join(statement.omitColumnMap, " ,") +} + // Init reset all the statement's fields -func (statement *Statement) Init() { +func (statement *Statement) Reset() { statement.RefTable = nil statement.Start = 0 statement.LimitN = nil @@ -75,7 +87,6 @@ func (statement *Statement) Init() { statement.GroupByStr = "" statement.HavingStr = "" statement.ColumnStr = "" - statement.OmitStr = "" statement.columnMap = columnMap{} statement.omitColumnMap = columnMap{} statement.AltTableName = "" @@ -144,6 +155,10 @@ func (statement *Statement) Where(query interface{}, args ...interface{}) *State 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 query.(type) { @@ -154,7 +169,7 @@ func (statement *Statement) And(query interface{}, args ...interface{}) *Stateme queryMap := query.(map[string]interface{}) newMap := make(map[string]interface{}) for k, v := range queryMap { - newMap[statement.Engine.Quote(k)] = v + newMap[statement.quote(k)] = v } statement.cond = statement.cond.And(builder.Eq(newMap)) case builder.Cond: @@ -197,14 +212,14 @@ func (statement *Statement) Or(query interface{}, args ...interface{}) *Statemen // In generate "Where column IN (?) " statement func (statement *Statement) In(column string, args ...interface{}) *Statement { - in := builder.In(statement.Engine.Quote(column), args...) + 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.Engine.Quote(column), args...) + notIn := builder.NotIn(statement.quote(column), args...) statement.cond = statement.cond.And(notIn) return statement } @@ -341,7 +356,7 @@ func (statement *Statement) buildUpdates(bean interface{}, if fieldValue.IsNil() { if includeNil { args = append(args, nil) - colNames = append(colNames, fmt.Sprintf("%v=?", engine.Quote(col.Name))) + colNames = append(colNames, fmt.Sprintf("%v=?", statement.quote(col.Name))) } continue } else if !fieldValue.IsValid() { @@ -485,10 +500,10 @@ func (statement *Statement) buildUpdates(bean interface{}, APPEND: args = append(args, val) - if col.IsPrimaryKey && engine.dialect.DBType() == "ql" { + if col.IsPrimaryKey { continue } - colNames = append(colNames, fmt.Sprintf("%v = ?", engine.Quote(col.Name))) + colNames = append(colNames, fmt.Sprintf("%v = ?", statement.quote(col.Name))) } return colNames, args @@ -504,9 +519,9 @@ func (statement *Statement) colName(col *schemas.Column, tableName string) strin if len(statement.TableAlias) > 0 { nm = statement.TableAlias } - return statement.Engine.Quote(nm) + "." + statement.Engine.Quote(col.Name) + return statement.quote(nm) + "." + statement.quote(col.Name) } - return statement.Engine.Quote(col.Name) + return statement.quote(col.Name) } // TableName return current tableName @@ -572,24 +587,6 @@ func (statement *Statement) SetExpr(column string, expression interface{}) *Stat return statement } -func (statement *Statement) col2NewColsWithQuote(columns ...string) []string { - newColumns := make([]string, 0) - quotes := append(strings.Split(statement.Engine.Quote(""), ""), "`") - for _, col := range columns { - newColumns = append(newColumns, statement.Engine.Quote(eraseAny(col, quotes...))) - } - return newColumns -} - -func (statement *Statement) colmap2NewColsWithQuote() []string { - newColumns := make([]string, len(statement.columnMap), len(statement.columnMap)) - copy(newColumns, statement.columnMap) - for i := 0; i < len(statement.columnMap); i++ { - newColumns[i] = statement.Engine.Quote(newColumns[i]) - } - return newColumns -} - // Distinct generates "DISTINCT col1, col2 " statement func (statement *Statement) Distinct(columns ...string) *Statement { statement.IsDistinct = true @@ -616,10 +613,7 @@ func (statement *Statement) Cols(columns ...string) *Statement { statement.columnMap.add(nc) } - newColumns := statement.colmap2NewColsWithQuote() - - statement.ColumnStr = strings.Join(newColumns, ", ") - statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.dialect.Quoter().Quote("*"), "*", -1) + statement.ColumnStr = statement.dialect.Quoter().Join(statement.columnMap, ", ") return statement } @@ -654,7 +648,6 @@ func (statement *Statement) Omit(columns ...string) { for _, nc := range newColumns { statement.omitColumnMap = append(statement.omitColumnMap, nc) } - statement.OmitStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", "))) } // Nullable Update use only: update columns to null when value is nullable and zero-value @@ -695,8 +688,13 @@ func (statement *Statement) Desc(colNames ...string) *Statement { if len(statement.OrderStr) > 0 { fmt.Fprint(&buf, statement.OrderStr, ", ") } - newColNames := statement.col2NewColsWithQuote(colNames...) - fmt.Fprintf(&buf, "%v DESC", strings.Join(newColNames, " DESC, ")) + 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 } @@ -707,8 +705,13 @@ func (statement *Statement) Asc(colNames ...string) *Statement { if len(statement.OrderStr) > 0 { fmt.Fprint(&buf, statement.OrderStr, ", ") } - newColNames := statement.col2NewColsWithQuote(colNames...) - fmt.Fprintf(&buf, "%v ASC", strings.Join(newColNames, " ASC, ")) + 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/statement_test.go b/statement_test.go index bc5fc5dd..6e5564b0 100644 --- a/statement_test.go +++ b/statement_test.go @@ -166,15 +166,17 @@ func (TestType) TableName() string { func createTestStatement() *Statement { if engine, ok := testEngine.(*Engine); ok { statement := &Statement{} - statement.Init() + statement.Reset() statement.Engine = engine + statement.dialect = engine.dialect statement.setRefValue(reflect.ValueOf(TestType{})) return statement } else if eg, ok := testEngine.(*EngineGroup); ok { statement := &Statement{} - statement.Init() + statement.Reset() statement.Engine = eg.Engine + statement.dialect = eg.Engine.dialect statement.setRefValue(reflect.ValueOf(TestType{})) return statement @@ -232,17 +234,12 @@ func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { }) assert.NoError(t, err) - record := assertGetRecord() - record.OnlyFromDBField = "test" - testEngine.Update(record) + assertGetRecord() + + _, err = testEngine.ID(1).Update(&TestOnlyFromDBField{ + OnlyToDBField: "b", + OnlyFromDBField: "test", + }) + assert.NoError(t, err) assertGetRecord() } - -func TestCol2NewColsWithQuote(t *testing.T) { - cols := []string{"f1", "f2", "t3.f3"} - - statement := createTestStatement() - - quotedCols := statement.col2NewColsWithQuote(cols...) - assert.EqualValues(t, []string{statement.Engine.Quote("f1"), statement.Engine.Quote("f2"), statement.Engine.Quote("t3.f3")}, quotedCols) -} From a421331cf9d76a7bc3b3a87563c2b4f53d44d109 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 01:30:06 +0000 Subject: [PATCH 034/112] Improve some codes (#1551) Improve code Reviewed-on: https://gitea.com/xorm/xorm/pulls/1551 --- helpers.go | 14 -------------- helpers_test.go | 18 ------------------ schemas/quote.go | 2 +- schemas/quote_test.go | 6 ++++++ session_find.go | 2 +- session_query.go | 2 +- session_update.go | 12 +++--------- statement.go | 12 ++++++------ 8 files changed, 18 insertions(+), 50 deletions(-) delete mode 100644 helpers_test.go diff --git a/helpers.go b/helpers.go index 75393ae3..32505c2c 100644 --- a/helpers.go +++ b/helpers.go @@ -200,17 +200,3 @@ func sliceEq(left, right []string) bool { func indexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } - -func eraseAny(value string, strToErase ...string) string { - if len(strToErase) == 0 { - return value - } - var replaceSeq []string - for _, s := range strToErase { - replaceSeq = append(replaceSeq, s, "") - } - - replacer := strings.NewReplacer(replaceSeq...) - - return replacer.Replace(value) -} diff --git a/helpers_test.go b/helpers_test.go deleted file mode 100644 index fc9ece27..00000000 --- a/helpers_test.go +++ /dev/null @@ -1,18 +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 ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEraseAny(t *testing.T) { - raw := "SELECT * FROM `table`.[table_name]" - assert.EqualValues(t, raw, eraseAny(raw)) - assert.EqualValues(t, "SELECT * FROM table.[table_name]", eraseAny(raw, "`")) - assert.EqualValues(t, "SELECT * FROM table.table_name", eraseAny(raw, "`", "[", "]")) -} diff --git a/schemas/quote.go b/schemas/quote.go index 21327eb0..d10a5dc8 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -69,7 +69,7 @@ func (q Quoter) Trim(s string) string { if s[0:1] == q[0] { s = s[1:] } - if len(s) > 0 && s[len(s)-1:] == q[0] { + if len(s) > 0 && s[len(s)-1:] == q[1] { return s[:len(s)-1] } return s diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 0c87d3a8..174d1a0d 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -63,3 +63,9 @@ func TestStrings(t *testing.T) { quotedCols := quoter.Strings(cols) assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols) } + +func TestTrim(t *testing.T) { + raw := "[table_name]" + assert.EqualValues(t, raw, CommonQuoter.Trim(raw)) + assert.EqualValues(t, "table_name", Quoter{"[", "]"}.Trim(raw)) +} diff --git a/session_find.go b/session_find.go index 566e83dd..251691b1 100644 --- a/session_find.go +++ b/session_find.go @@ -135,7 +135,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) return ErrTableNotFound } - var columnStr = session.statement.ColumnStr + var columnStr = session.statement.columnStr() if len(session.statement.selectStr) > 0 { columnStr = session.statement.selectStr } else { diff --git a/session_query.go b/session_query.go index afed4bcb..1783e154 100644 --- a/session_query.go +++ b/session_query.go @@ -29,7 +29,7 @@ func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interfa return "", nil, ErrTableNotFound } - var columnStr = session.statement.ColumnStr + var columnStr = session.statement.columnStr() if len(session.statement.selectStr) > 0 { columnStr = session.statement.selectStr } else { diff --git a/session_update.go b/session_update.go index 74b180d5..c95b23c5 100644 --- a/session_update.go +++ b/session_update.go @@ -103,14 +103,8 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri sps := strings.SplitN(kv, "=", 2) sps2 := strings.Split(sps[0], ".") colName := sps2[len(sps2)-1] - // treat quote prefix, suffix and '`' as quotes - quotes := append(strings.Split(session.engine.Quote(""), ""), "`") - if strings.ContainsAny(colName, strings.Join(quotes, "")) { - colName = strings.TrimSpace(eraseAny(colName, quotes...)) - } else { - session.engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName) - return ErrCacheFailed - } + colName = session.engine.dialect.Quoter().Trim(colName) + colName = schemas.CommonQuoter.Trim(colName) if col := table.GetColumn(colName); col != nil { fieldValue, err := col.ValueOf(bean) @@ -182,7 +176,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 return 0, ErrTableNotFound } - if session.statement.ColumnStr == "" { + if session.statement.columnStr() == "" { colNames, args = session.statement.buildUpdates(bean, false, false, false, false, true) } else { diff --git a/statement.go b/statement.go index fd6b3962..651ce175 100644 --- a/statement.go +++ b/statement.go @@ -30,7 +30,6 @@ type Statement struct { joinArgs []interface{} GroupByStr string HavingStr string - ColumnStr string selectStr string useAllCols bool AltTableName string @@ -86,7 +85,6 @@ func (statement *Statement) Reset() { statement.joinArgs = make([]interface{}, 0) statement.GroupByStr = "" statement.HavingStr = "" - statement.ColumnStr = "" statement.columnMap = columnMap{} statement.omitColumnMap = columnMap{} statement.AltTableName = "" @@ -612,11 +610,13 @@ func (statement *Statement) Cols(columns ...string) *Statement { for _, nc := range cols { statement.columnMap.add(nc) } - - statement.ColumnStr = statement.dialect.Quoter().Join(statement.columnMap, ", ") return statement } +func (statement *Statement) columnStr() string { + return statement.Engine.dialect.Quoter().Join(statement.columnMap, ", ") +} + // AllCols update use only: update all columns func (statement *Statement) AllCols() *Statement { statement.useAllCols = true @@ -955,7 +955,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, statement.setRefBean(bean) } - var columnStr = statement.ColumnStr + var columnStr = statement.columnStr() if len(statement.selectStr) > 0 { columnStr = statement.selectStr } else { @@ -1020,7 +1020,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa var selectSQL = statement.selectStr if len(selectSQL) <= 0 { if statement.IsDistinct { - selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr) + selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.columnStr()) } else { selectSQL = "count(*)" } From 02c8a4b25da8488fc2e84067b9e1499f58cb0997 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 03:12:25 +0000 Subject: [PATCH 035/112] Move caches to manager (#1553) Move caches to manager Reviewed-on: https://gitea.com/xorm/xorm/pulls/1553 --- caches/manager.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ engine.go | 55 +++++++++++++--------------------------------- session_delete.go | 4 ++-- session_find.go | 4 ++-- session_get.go | 4 ++-- session_insert.go | 2 +- session_update.go | 4 ++-- xorm.go | 2 +- 8 files changed, 81 insertions(+), 50 deletions(-) create mode 100644 caches/manager.go diff --git a/caches/manager.go b/caches/manager.go new file mode 100644 index 00000000..05045210 --- /dev/null +++ b/caches/manager.go @@ -0,0 +1,56 @@ +// Copyright 2020 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 caches + +import "sync" + +type Manager struct { + cacher Cacher + disableGlobalCache bool + + cachers map[string]Cacher + cacherLock sync.RWMutex +} + +func NewManager() *Manager { + return &Manager{ + cachers: make(map[string]Cacher), + } +} + +// SetDisableGlobalCache disable global cache or not +func (mgr *Manager) SetDisableGlobalCache(disable bool) { + if mgr.disableGlobalCache != disable { + mgr.disableGlobalCache = disable + } +} + +func (mgr *Manager) SetCacher(tableName string, cacher Cacher) { + mgr.cacherLock.Lock() + mgr.cachers[tableName] = cacher + mgr.cacherLock.Unlock() +} + +func (mgr *Manager) GetCacher(tableName string) Cacher { + var cacher Cacher + var ok bool + mgr.cacherLock.RLock() + cacher, ok = mgr.cachers[tableName] + mgr.cacherLock.RUnlock() + if !ok && !mgr.disableGlobalCache { + cacher = mgr.cacher + } + return cacher +} + +// SetDefaultCacher set the default cacher. Xorm's default not enable cacher. +func (mgr *Manager) SetDefaultCacher(cacher Cacher) { + mgr.cacher = cacher +} + +// GetDefaultCacher returns the default cacher +func (mgr *Manager) GetDefaultCacher() Cacher { + return mgr.cacher +} diff --git a/engine.go b/engine.go index 1ab2eed2..68de5196 100644 --- a/engine.go +++ b/engine.go @@ -40,8 +40,7 @@ type Engine struct { TagIdentifier string Tables map[reflect.Type]*schemas.Table - mutex *sync.RWMutex - Cacher caches.Cacher + mutex *sync.RWMutex showSQL bool showExecTime bool @@ -50,42 +49,20 @@ type Engine struct { TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database - disableGlobalCache bool - tagHandlers map[string]tagHandler engineGroup *EngineGroup - - cachers map[string]caches.Cacher - cacherLock sync.RWMutex + cacherMgr *caches.Manager defaultContext context.Context } -func (engine *Engine) setCacher(tableName string, cacher caches.Cacher) { - engine.cacherLock.Lock() - engine.cachers[tableName] = cacher - engine.cacherLock.Unlock() -} - func (engine *Engine) SetCacher(tableName string, cacher caches.Cacher) { - engine.setCacher(tableName, cacher) -} - -func (engine *Engine) getCacher(tableName string) caches.Cacher { - var cacher caches.Cacher - var ok bool - engine.cacherLock.RLock() - cacher, ok = engine.cachers[tableName] - engine.cacherLock.RUnlock() - if !ok && !engine.disableGlobalCache { - cacher = engine.Cacher - } - return cacher + engine.cacherMgr.SetCacher(tableName, cacher) } func (engine *Engine) GetCacher(tableName string) caches.Cacher { - return engine.getCacher(tableName) + return engine.cacherMgr.GetCacher(tableName) } // BufferSize sets buffer size for iterate @@ -152,9 +129,7 @@ func (engine *Engine) SetLogLevel(level log.LogLevel) { // SetDisableGlobalCache disable global cache or not func (engine *Engine) SetDisableGlobalCache(disable bool) { - if engine.disableGlobalCache != disable { - engine.disableGlobalCache = disable - } + engine.cacherMgr.SetDisableGlobalCache(disable) } // DriverName return the current sql driver's name @@ -244,12 +219,12 @@ func (engine *Engine) SetMaxIdleConns(conns int) { // SetDefaultCacher set the default cacher. Xorm's default not enable cacher. func (engine *Engine) SetDefaultCacher(cacher caches.Cacher) { - engine.Cacher = cacher + engine.cacherMgr.SetDefaultCacher(cacher) } // GetDefaultCacher returns the default cacher func (engine *Engine) GetDefaultCacher() caches.Cacher { - return engine.Cacher + return engine.cacherMgr.GetDefaultCacher() } // NoCache If you has set default cacher, and you want temporilly stop use cache, @@ -269,7 +244,7 @@ func (engine *Engine) NoCascade() *Session { // MapCacher Set a table use a special cacher func (engine *Engine) MapCacher(bean interface{}, cacher caches.Cacher) error { - engine.setCacher(engine.TableName(bean, true), cacher) + engine.SetCacher(engine.TableName(bean, true), cacher) return nil } @@ -799,7 +774,7 @@ func (engine *Engine) autoMapType(v reflect.Value) (*schemas.Table, error) { } engine.Tables[t] = table - if engine.Cacher != nil { + if engine.GetDefaultCacher() != nil { if v.CanAddr() { engine.GobRegister(v.Addr().Interface()) } else { @@ -1030,17 +1005,17 @@ func (engine *Engine) mapType(v reflect.Value) (*schemas.Table, error) { } if hasCacheTag { - if engine.Cacher != nil { // !nash! use engine's cacher if provided + if engine.GetDefaultCacher() != nil { // !nash! use engine's cacher if provided engine.logger.Info("enable cache on table:", table.Name) - engine.setCacher(table.Name, engine.Cacher) + engine.SetCacher(table.Name, engine.GetDefaultCacher()) } else { engine.logger.Info("enable LRU cache on table:", table.Name) - engine.setCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) + engine.SetCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) } } if hasNoCacheTag { engine.logger.Info("disable cache on table:", table.Name) - engine.setCacher(table.Name, nil) + engine.SetCacher(table.Name, nil) } return table, nil @@ -1152,7 +1127,7 @@ func (engine *Engine) CreateUniques(bean interface{}) error { // ClearCacheBean if enabled cache, clear the cache bean func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { tableName := engine.TableName(bean) - cacher := engine.getCacher(tableName) + cacher := engine.GetCacher(tableName) if cacher != nil { cacher.ClearIds(tableName) cacher.DelBean(tableName, id) @@ -1164,7 +1139,7 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { func (engine *Engine) ClearCache(beans ...interface{}) error { for _, bean := range beans { tableName := engine.TableName(bean) - cacher := engine.getCacher(tableName) + cacher := engine.GetCacher(tableName) if cacher != nil { cacher.ClearIds(tableName) cacher.ClearBeans(tableName) diff --git a/session_delete.go b/session_delete.go index 2afe068d..94cf833d 100644 --- a/session_delete.go +++ b/session_delete.go @@ -28,7 +28,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri return ErrCacheFailed } - cacher := session.engine.getCacher(tableName) + cacher := session.engine.GetCacher(tableName) pkColumns := table.PKColumns() ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { @@ -206,7 +206,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { }) } - if cacher := session.engine.getCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache { + if cacher := session.engine.GetCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache { session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) } diff --git a/session_find.go b/session_find.go index 251691b1..cdf086d0 100644 --- a/session_find.go +++ b/session_find.go @@ -183,7 +183,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } if session.canCache() { - if cacher := session.engine.getCacher(session.statement.TableName()); cacher != nil && + if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && !session.statement.IsDistinct && !session.statement.unscoped { err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) @@ -329,7 +329,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } tableName := session.statement.TableName() - cacher := session.engine.getCacher(tableName) + cacher := session.engine.GetCacher(tableName) if cacher == nil { return nil } diff --git a/session_get.go b/session_get.go index c42361a8..fd66c438 100644 --- a/session_get.go +++ b/session_get.go @@ -65,7 +65,7 @@ func (session *Session) get(bean interface{}) (bool, error) { table := session.statement.RefTable if session.canCache() && beanValue.Elem().Kind() == reflect.Struct { - if cacher := session.engine.getCacher(session.statement.TableName()); cacher != nil && + if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && !session.statement.unscoped { has, err := session.cacheGet(bean, sqlStr, args...) if err != ErrCacheFailed { @@ -280,7 +280,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } tableName := session.statement.TableName() - cacher := session.engine.getCacher(tableName) + cacher := session.engine.GetCacher(tableName) session.engine.logger.Debug("[cache] Get SQL:", newsql, args) table := session.statement.RefTable diff --git a/session_insert.go b/session_insert.go index b788e629..fcd7b2b5 100644 --- a/session_insert.go +++ b/session_insert.go @@ -613,7 +613,7 @@ func (session *Session) cacheInsert(table string) error { if !session.statement.UseCache { return nil } - cacher := session.engine.getCacher(table) + cacher := session.engine.GetCacher(table) if cacher == nil { return nil } diff --git a/session_update.go b/session_update.go index c95b23c5..c1f1e0bf 100644 --- a/session_update.go +++ b/session_update.go @@ -42,7 +42,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri } } - cacher := session.engine.getCacher(tableName) + cacher := session.engine.GetCacher(tableName) session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) ids, err := caches.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { @@ -412,7 +412,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache { + if cacher := session.engine.GetCacher(tableName); cacher != nil && session.statement.UseCache { // session.cacheUpdate(table, tableName, sqlStr, args...) session.engine.logger.Debug("[cacheUpdate] clear table ", tableName) cacher.ClearIds(tableName) diff --git a/xorm.go b/xorm.go index e7e98739..0f27f8e4 100644 --- a/xorm.go +++ b/xorm.go @@ -68,8 +68,8 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { TagIdentifier: "xorm", TZLocation: time.Local, tagHandlers: defaultTagHandlers, - cachers: make(map[string]caches.Cacher), defaultContext: context.Background(), + cacherMgr: caches.NewManager(), } if uri.DBType == schemas.SQLITE { From aa522f7d98ad391d9eb71fffe9337953542452bd Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 03:33:40 +0000 Subject: [PATCH 036/112] Improve code (#1552) Change filters Fix test add more tests on drone add more tests on drone Improve code Reviewed-on: https://gitea.com/xorm/xorm/pulls/1552 --- .drone.yml | 2 ++ core/db_test.go | 7 ++++--- dialects/filter.go | 12 ++++++------ dialects/filter_test.go | 14 ++++---------- dialects/mssql.go | 2 +- dialects/oracle.go | 5 ++++- dialects/postgres.go | 2 +- engine.go | 3 ++- helpers.go | 5 +++++ helpler_time.go | 21 --------------------- interface.go | 25 +++++++++++++++++++++++++ internal/utils/zero.go | 11 +++++++++++ json.go | 31 ------------------------------- session_convert.go | 3 ++- session_delete.go | 2 +- session_find.go | 2 +- session_get.go | 2 +- session_raw.go | 2 +- session_update.go | 2 +- time_test.go | 17 +++++++++-------- 20 files changed, 81 insertions(+), 89 deletions(-) delete mode 100644 helpler_time.go delete mode 100644 json.go diff --git a/.drone.yml b/.drone.yml index 7a273d3d..3f859ee0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,6 +22,8 @@ steps: commands: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite + - go test ./caches/... ./core/... ./dialects/... ./internal/... \ + ./log/... ./migrate/... ./names/... ./schemas/... when: event: - push diff --git a/core/db_test.go b/core/db_test.go index 9abc7a64..777ab0ad 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -11,14 +11,15 @@ import ( "testing" "time" + "xorm.io/xorm/names" + _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" - "xorm.io/xorm/names" ) var ( - dbtype = flag.String("dbtype", "mysql", "database type") - dbConn = flag.String("dbConn", "root:@/core_test?charset=utf8", "database connect string") + dbtype = flag.String("dbtype", "sqlite3", "database type") + dbConn = flag.String("dbConn", "./db_test.db", "database connect string") createTableSql string ) diff --git a/dialects/filter.go b/dialects/filter.go index 4795edb7..0f9b4107 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -13,20 +13,20 @@ import ( // Filter is an interface to filter SQL type Filter interface { - Do(sql string, dialect Dialect, table *schemas.Table) string + Do(sql string) string } // QuoteFilter filter SQL replace ` to database's own quote character type QuoteFilter struct { + quoter schemas.Quoter } -func (s *QuoteFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { - quoter := dialect.Quoter() - if quoter.IsEmpty() { +func (s *QuoteFilter) Do(sql string) string { + if s.quoter.IsEmpty() { return sql } - prefix, suffix := quoter[0][0], quoter[1][0] + prefix, suffix := s.quoter[0][0], s.quoter[1][0] raw := []byte(sql) for i, cnt := 0, 0; i < len(raw); i = i + 1 { if raw[i] == '`' { @@ -66,6 +66,6 @@ func convertQuestionMark(sql, prefix string, start int) string { return buf.String() } -func (s *SeqFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { +func (s *SeqFilter) Do(sql string) string { return convertQuestionMark(sql, s.Prefix, s.Start) } diff --git a/dialects/filter_test.go b/dialects/filter_test.go index e5430bab..ac110a69 100644 --- a/dialects/filter_test.go +++ b/dialects/filter_test.go @@ -3,21 +3,15 @@ package dialects import ( "testing" + "xorm.io/xorm/schemas" + "github.com/stretchr/testify/assert" ) -type quoterOnly struct { - Dialect -} - -func (q *quoterOnly) Quote(item string) string { - return "[" + item + "]" -} - func TestQuoteFilter_Do(t *testing.T) { - f := QuoteFilter{} + f := QuoteFilter{schemas.Quoter{"[", "]"}} sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" - res := f.Do(sql, new(quoterOnly), nil) + res := f.Do(sql) assert.EqualValues(t, "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", res, diff --git a/dialects/mssql.go b/dialects/mssql.go index d473d975..74a3bb63 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -534,7 +534,7 @@ func (db *mssql) ForUpdateSQL(query string) string { } func (db *mssql) Filters() []Filter { - return []Filter{&QuoteFilter{}} + return []Filter{&QuoteFilter{db.Quoter()}} } type odbcDriver struct { diff --git a/dialects/oracle.go b/dialects/oracle.go index bf9ee2af..46f7aca2 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -848,7 +848,10 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*schemas.Index, error } func (db *oracle) Filters() []Filter { - return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: ":", Start: 1}} + return []Filter{ + &QuoteFilter{db.Quoter()}, + &SeqFilter{Prefix: ":", Start: 1}, + } } type goracleDriver struct { diff --git a/dialects/postgres.go b/dialects/postgres.go index f161fdfa..cab7eaef 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1159,7 +1159,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*schemas.Index, err } func (db *postgres) Filters() []Filter { - return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: "$", Start: 1}} + return []Filter{&QuoteFilter{db.Quoter()}, &SeqFilter{Prefix: "$", Start: 1}} } type pqDriver struct { diff --git a/engine.go b/engine.go index 68de5196..242e3830 100644 --- a/engine.go +++ b/engine.go @@ -24,6 +24,7 @@ import ( "xorm.io/xorm/caches" "xorm.io/xorm/core" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/log" "xorm.io/xorm/names" "xorm.io/xorm/schemas" @@ -80,7 +81,7 @@ func (engine *Engine) CondDeleted(col *schemas.Column) builder.Cond { } else { // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. if engine.dialect.DBType() != schemas.MSSQL { - cond = builder.Eq{col.Name: zeroTime1} + cond = builder.Eq{col.Name: utils.ZeroTime1} } } diff --git a/helpers.go b/helpers.go index 32505c2c..e9a3a646 100644 --- a/helpers.go +++ b/helpers.go @@ -11,6 +11,7 @@ import ( "sort" "strconv" "strings" + "time" ) // str2PK convert string value to primary key value according to tp @@ -200,3 +201,7 @@ func sliceEq(left, right []string) bool { func indexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } + +func formatTime(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} diff --git a/helpler_time.go b/helpler_time.go deleted file mode 100644 index f4013e27..00000000 --- a/helpler_time.go +++ /dev/null @@ -1,21 +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 "time" - -const ( - zeroTime0 = "0000-00-00 00:00:00" - zeroTime1 = "0001-01-01 00:00:00" -) - -func formatTime(t time.Time) string { - return t.Format("2006-01-02 15:04:05") -} - -func isTimeZero(t time.Time) bool { - return t.IsZero() || formatTime(t) == zeroTime0 || - formatTime(t) == zeroTime1 -} diff --git a/interface.go b/interface.go index 43b4b5f4..d7e5b778 100644 --- a/interface.go +++ b/interface.go @@ -7,6 +7,7 @@ package xorm import ( "context" "database/sql" + "encoding/json" "reflect" "time" @@ -122,3 +123,27 @@ var ( _ EngineInterface = &Engine{} _ EngineInterface = &EngineGroup{} ) + +// JSONInterface represents an interface to handle json data +type JSONInterface interface { + Marshal(v interface{}) ([]byte, error) + Unmarshal(data []byte, v interface{}) error +} + +var ( + // DefaultJSONHandler default json handler + DefaultJSONHandler JSONInterface = StdJSON{} +) + +// StdJSON implements JSONInterface via encoding/json +type StdJSON struct{} + +// Marshal implements JSONInterface +func (StdJSON) Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +// Unmarshal implements JSONInterface +func (StdJSON) Unmarshal(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} diff --git a/internal/utils/zero.go b/internal/utils/zero.go index 8fd1870c..5415fc15 100644 --- a/internal/utils/zero.go +++ b/internal/utils/zero.go @@ -6,6 +6,7 @@ package utils import ( "reflect" + "time" ) type Zeroable interface { @@ -96,3 +97,13 @@ func IsArrayZero(v reflect.Value) bool { return true } + +const ( + ZeroTime0 = "0000-00-00 00:00:00" + ZeroTime1 = "0001-01-01 00:00:00" +) + +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/json.go b/json.go deleted file mode 100644 index fdb6ce56..00000000 --- a/json.go +++ /dev/null @@ -1,31 +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 xorm - -import "encoding/json" - -// JSONInterface represents an interface to handle json data -type JSONInterface interface { - Marshal(v interface{}) ([]byte, error) - Unmarshal(data []byte, v interface{}) error -} - -var ( - // DefaultJSONHandler default json handler - DefaultJSONHandler JSONInterface = StdJSON{} -) - -// StdJSON implements JSONInterface via encoding/json -type StdJSON struct{} - -// Marshal implements JSONInterface -func (StdJSON) Marshal(v interface{}) ([]byte, error) { - return json.Marshal(v) -} - -// Unmarshal implements JSONInterface -func (StdJSON) Unmarshal(data []byte, v interface{}) error { - return json.Unmarshal(data, v) -} diff --git a/session_convert.go b/session_convert.go index 020507ed..1c19092d 100644 --- a/session_convert.go +++ b/session_convert.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -27,7 +28,7 @@ func (session *Session) str2Time(col *schemas.Column, data string) (outTime time parseLoc = col.TimeZone } - if sdata == zeroTime0 || sdata == zeroTime1 { + 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) diff --git a/session_delete.go b/session_delete.go index 94cf833d..a639d61f 100644 --- a/session_delete.go +++ b/session_delete.go @@ -20,7 +20,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri } for _, filter := range session.engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr, session.engine.dialect, table) + sqlStr = filter.Do(sqlStr) } newsql := session.statement.convertIDSQL(sqlStr) diff --git a/session_find.go b/session_find.go index cdf086d0..fd1d49b1 100644 --- a/session_find.go +++ b/session_find.go @@ -335,7 +335,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } for _, filter := range session.engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable) + sqlStr = filter.Do(sqlStr) } newsql := session.statement.convertIDSQL(sqlStr) diff --git a/session_get.go b/session_get.go index fd66c438..bf91eacf 100644 --- a/session_get.go +++ b/session_get.go @@ -272,7 +272,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } for _, filter := range session.engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable) + sqlStr = filter.Do(sqlStr) } newsql := session.statement.convertIDSQL(sqlStr) if newsql == "" { diff --git a/session_raw.go b/session_raw.go index 4a2e2777..51487779 100644 --- a/session_raw.go +++ b/session_raw.go @@ -15,7 +15,7 @@ import ( func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { for _, filter := range session.engine.dialect.Filters() { - *sqlStr = filter.Do(*sqlStr, session.engine.dialect, session.statement.RefTable) + *sqlStr = filter.Do(*sqlStr) } session.lastSQL = *sqlStr diff --git a/session_update.go b/session_update.go index c1f1e0bf..4330afae 100644 --- a/session_update.go +++ b/session_update.go @@ -28,7 +28,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri return ErrCacheFailed } for _, filter := range session.engine.dialect.Filters() { - newsql = filter.Do(newsql, session.engine.dialect, table) + newsql = filter.Do(newsql) } session.engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql) diff --git a/time_test.go b/time_test.go index 6ee97fe1..44a84553 100644 --- a/time_test.go +++ b/time_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/internal/utils" ) func TestTimeUserTime(t *testing.T) { @@ -282,7 +283,7 @@ func TestTimeUserDeleted(t *testing.T) { assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) - assert.True(t, isTimeZero(user2.DeletedAt)) + assert.True(t, utils.IsTimeZero(user2.DeletedAt)) fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) fmt.Println("user2 str", user2.CreatedAtStr, user2.UpdatedAtStr) @@ -290,7 +291,7 @@ func TestTimeUserDeleted(t *testing.T) { cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.True(t, !isTimeZero(user3.DeletedAt)) + assert.True(t, !utils.IsTimeZero(user3.DeletedAt)) var user4 UserDeleted has, err = testEngine.Unscoped().Get(&user4) @@ -336,14 +337,14 @@ func TestTimeUserDeletedDiffLoc(t *testing.T) { assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) - assert.True(t, isTimeZero(user2.DeletedAt)) + assert.True(t, utils.IsTimeZero(user2.DeletedAt)) fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) var user3 UserDeleted2 cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.True(t, !isTimeZero(user3.DeletedAt)) + assert.True(t, !utils.IsTimeZero(user3.DeletedAt)) var user4 UserDeleted2 has, err = testEngine.Unscoped().Get(&user4) @@ -407,14 +408,14 @@ func TestCustomTimeUserDeleted(t *testing.T) { assert.EqualValues(t, formatTime(time.Time(user.CreatedAt)), formatTime(time.Time(user2.CreatedAt))) assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) assert.EqualValues(t, formatTime(time.Time(user.UpdatedAt)), formatTime(time.Time(user2.UpdatedAt))) - assert.True(t, isTimeZero(time.Time(user2.DeletedAt))) + assert.True(t, utils.IsTimeZero(time.Time(user2.DeletedAt))) fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) var user3 UserDeleted3 cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.True(t, !isTimeZero(time.Time(user3.DeletedAt))) + assert.True(t, !utils.IsTimeZero(time.Time(user3.DeletedAt))) var user4 UserDeleted3 has, err = testEngine.Unscoped().Get(&user4) @@ -460,14 +461,14 @@ func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { assert.EqualValues(t, formatTime(time.Time(user.CreatedAt)), formatTime(time.Time(user2.CreatedAt))) assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) assert.EqualValues(t, formatTime(time.Time(user.UpdatedAt)), formatTime(time.Time(user2.UpdatedAt))) - assert.True(t, isTimeZero(time.Time(user2.DeletedAt))) + assert.True(t, utils.IsTimeZero(time.Time(user2.DeletedAt))) fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) var user3 UserDeleted4 cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.True(t, !isTimeZero(time.Time(user3.DeletedAt))) + assert.True(t, !utils.IsTimeZero(time.Time(user3.DeletedAt))) var user4 UserDeleted4 has, err = testEngine.Unscoped().Get(&user4) From e2f910041961e080fb188c698ac2b58a6c7a7ecc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 03:58:31 +0000 Subject: [PATCH 037/112] Move tag parser related codes as a standalone sub package (#1547) Fix sliceEq fix tests Move tag parser related codes as a standalone sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1547 --- .drone.yml | 4 +- convert.go | 7 - convert/conversion.go | 12 + engine.go | 231 +----- engine_cond.go | 3 +- engine_group.go | 8 +- engine_table.go | 14 +- helpers.go | 15 - internal/utils/slice.go | 22 + session.go | 7 +- session_convert.go | 9 +- session_delete.go | 2 +- session_find.go | 2 +- session_get.go | 2 +- session_insert.go | 2 +- session_schema.go | 5 +- statement.go | 5 +- tag_cache_test.go | 35 - tag_extends_test.go | 608 ---------------- tag_id_test.go | 85 --- tag_test.go | 600 ---------------- tag_version_test.go | 242 ------- tags/parser.go | 239 +++++++ tag.go => tags/tag.go | 58 +- tags/tag_test.go | 30 + tags_test.go | 1501 +++++++++++++++++++++++++++++++++++++++ types_test.go | 18 +- xorm.go | 6 +- 28 files changed, 1890 insertions(+), 1882 deletions(-) create mode 100644 convert/conversion.go create mode 100644 internal/utils/slice.go delete mode 100644 tag_cache_test.go delete mode 100644 tag_extends_test.go delete mode 100644 tag_id_test.go delete mode 100644 tag_test.go delete mode 100644 tag_version_test.go create mode 100644 tags/parser.go rename tag.go => tags/tag.go (83%) create mode 100644 tags/tag_test.go create mode 100644 tags_test.go diff --git a/.drone.yml b/.drone.yml index 3f859ee0..ef48cd60 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,8 +22,8 @@ steps: commands: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite - - go test ./caches/... ./core/... ./dialects/... ./internal/... \ - ./log/... ./migrate/... ./names/... ./schemas/... + - go test ./caches/... ./convert/... ./core/... ./dialects/... \ + ./log/... ./migrate/... ./names/... ./schemas/... ./tags/... when: event: - push diff --git a/convert.go b/convert.go index b8e1c4fc..2316ca0b 100644 --- a/convert.go +++ b/convert.go @@ -346,10 +346,3 @@ func asBool(bs []byte) (bool, error) { } return strconv.ParseBool(string(bs)) } - -// 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) -} diff --git a/convert/conversion.go b/convert/conversion.go new file mode 100644 index 00000000..16f1a92a --- /dev/null +++ b/convert/conversion.go @@ -0,0 +1,12 @@ +// 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 convert + +// 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) +} diff --git a/engine.go b/engine.go index 242e3830..b97d1c06 100644 --- a/engine.go +++ b/engine.go @@ -28,6 +28,7 @@ import ( "xorm.io/xorm/log" "xorm.io/xorm/names" "xorm.io/xorm/schemas" + "xorm.io/xorm/tags" ) // Engine is the major struct of xorm, it means a database manager. @@ -36,10 +37,7 @@ type Engine struct { db *core.DB dialect dialects.Dialect - ColumnMapper names.Mapper - TableMapper names.Mapper - TagIdentifier string - Tables map[reflect.Type]*schemas.Table + Tables map[reflect.Type]*schemas.Table mutex *sync.RWMutex @@ -50,12 +48,12 @@ type Engine struct { TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database - tagHandlers map[string]tagHandler - engineGroup *EngineGroup - cacherMgr *caches.Manager defaultContext context.Context + + tagParser *tags.Parser + cacherMgr *caches.Manager } func (engine *Engine) SetCacher(tableName string, cacher caches.Cacher) { @@ -151,12 +149,12 @@ func (engine *Engine) SetMapper(mapper names.Mapper) { // SetTableMapper set the table name mapping rule func (engine *Engine) SetTableMapper(mapper names.Mapper) { - engine.TableMapper = mapper + engine.tagParser.TableMapper = mapper } // SetColumnMapper set the column name mapping rule func (engine *Engine) SetColumnMapper(mapper names.Mapper) { - engine.ColumnMapper = mapper + engine.tagParser.ColumnMapper = mapper } // SupportInsertMany If engine's database support batch insert records like @@ -769,7 +767,7 @@ func (engine *Engine) autoMapType(v reflect.Value) (*schemas.Table, error) { table, ok := engine.Tables[t] if !ok { var err error - table, err = engine.mapType(v) + table, err = engine.tagParser.MapType(v) if err != nil { return nil, err } @@ -813,215 +811,6 @@ func (engine *Engine) TableInfo(bean interface{}) *Table { return &Table{tb, engine.TableName(bean)} } -func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) { - if index, ok := table.Indexes[indexName]; ok { - index.AddColumn(col.Name) - col.Indexes[index.Name] = indexType - } else { - index := schemas.NewIndex(indexName, indexType) - index.AddColumn(col.Name) - table.AddIndex(index) - col.Indexes[index.Name] = indexType - } -} - -// TableName table name interface to define customerize table name -type TableName interface { - TableName() string -} - -var ( - tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() -) - -func (engine *Engine) mapType(v reflect.Value) (*schemas.Table, error) { - t := v.Type() - table := schemas.NewEmptyTable() - table.Type = t - table.Name = names.GetTableName(engine.TableMapper, v) - - var idFieldColName string - var hasCacheTag, hasNoCacheTag bool - - for i := 0; i < t.NumField(); i++ { - tag := t.Field(i).Tag - - ormTagStr := tag.Get(engine.TagIdentifier) - var col *schemas.Column - fieldValue := v.Field(i) - fieldType := fieldValue.Type() - - 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 = tagContext{ - table: table, - col: col, - fieldValue: fieldValue, - indexNames: make(map[string]int), - engine: engine, - } - - 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 := engine.tagHandlers[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) - } - engine.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 = engine.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().(Conversion); ok { - sqlType = schemas.SQLType{Name: schemas.Text} - } - } - if _, ok := fieldValue.Interface().(Conversion); ok { - sqlType = schemas.SQLType{Name: schemas.Text} - } else { - sqlType = schemas.Type2SQLType(fieldType) - } - col = schemas.NewColumn(engine.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 - } - - 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 engine.GetDefaultCacher() != nil { // !nash! use engine's cacher if provided - engine.logger.Info("enable cache on table:", table.Name) - engine.SetCacher(table.Name, engine.GetDefaultCacher()) - } else { - engine.logger.Info("enable LRU cache on table:", table.Name) - engine.SetCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) - } - } - if hasNoCacheTag { - engine.logger.Info("disable cache on table:", table.Name) - engine.SetCacher(table.Name, nil) - } - - return table, nil -} - // IsTableEmpty if a table has any reocrd func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { session := engine.NewSession() @@ -1542,12 +1331,12 @@ func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{} // GetColumnMapper returns the column name mapper func (engine *Engine) GetColumnMapper() names.Mapper { - return engine.ColumnMapper + return engine.tagParser.ColumnMapper } // GetTableMapper returns the table name mapper func (engine *Engine) GetTableMapper() names.Mapper { - return engine.TableMapper + return engine.tagParser.TableMapper } // GetTZLocation returns time zone of the application diff --git a/engine_cond.go b/engine_cond.go index 949d705d..00bfd59d 100644 --- a/engine_cond.go +++ b/engine_cond.go @@ -12,6 +12,7 @@ import ( "time" "xorm.io/builder" + "xorm.io/xorm/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -137,7 +138,7 @@ func (engine *Engine) buildConds(table *schemas.Table, bean interface{}, continue } val = engine.formatColTime(col, t) - } else if _, ok := reflect.New(fieldType).Interface().(Conversion); ok { + } else if _, ok := reflect.New(fieldType).Interface().(convert.Conversion); ok { continue } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = valNul.Value() diff --git a/engine_group.go b/engine_group.go index 24dc0103..55159d55 100644 --- a/engine_group.go +++ b/engine_group.go @@ -112,9 +112,9 @@ func (eg *EngineGroup) Ping() error { // SetColumnMapper set the column name mapping rule func (eg *EngineGroup) SetColumnMapper(mapper names.Mapper) { - eg.Engine.ColumnMapper = mapper + eg.Engine.SetColumnMapper(mapper) for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].ColumnMapper = mapper + eg.slaves[i].SetColumnMapper(mapper) } } @@ -182,9 +182,9 @@ func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup { // SetTableMapper set the table name mapping rule func (eg *EngineGroup) SetTableMapper(mapper names.Mapper) { - eg.Engine.TableMapper = mapper + eg.Engine.SetTableMapper(mapper) for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].TableMapper = mapper + eg.slaves[i].SetTableMapper(mapper) } } diff --git a/engine_table.go b/engine_table.go index e60d8fce..0954b2d3 100644 --- a/engine_table.go +++ b/engine_table.go @@ -72,13 +72,13 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { switch f.(type) { case string: table = f.(string) - case TableName: - table = f.(TableName).TableName() + case names.TableName: + table = f.(names.TableName).TableName() default: v := rValue(f) t := v.Type() if t.Kind() == reflect.Struct { - table = names.GetTableName(engine.TableMapper, v) + table = names.GetTableName(engine.GetTableMapper(), v) } else { table = engine.Quote(fmt.Sprintf("%v", f)) } @@ -90,18 +90,18 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { } else if l == 1 { return engine.Quote(table) } - case TableName: - return tablename.(TableName).TableName() + case names.TableName: + return tablename.(names.TableName).TableName() case string: return tablename.(string) case reflect.Value: v := tablename.(reflect.Value) - return names.GetTableName(engine.TableMapper, v) + return names.GetTableName(engine.GetTableMapper(), v) default: v := rValue(tablename) t := v.Type() if t.Kind() == reflect.Struct { - return names.GetTableName(engine.TableMapper, v) + return names.GetTableName(engine.GetTableMapper(), v) } return engine.Quote(fmt.Sprintf("%v", tablename)) } diff --git a/helpers.go b/helpers.go index e9a3a646..1401cbf2 100644 --- a/helpers.go +++ b/helpers.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "reflect" - "sort" "strconv" "strings" "time" @@ -184,20 +183,6 @@ func structName(v reflect.Type) string { return v.Name() } -func sliceEq(left, right []string) bool { - if len(left) != len(right) { - return false - } - sort.Sort(sort.StringSlice(left)) - sort.Sort(sort.StringSlice(right)) - for i := 0; i < len(left); i++ { - if left[i] != right[i] { - return false - } - } - return true -} - func indexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } diff --git a/internal/utils/slice.go b/internal/utils/slice.go new file mode 100644 index 00000000..89685706 --- /dev/null +++ b/internal/utils/slice.go @@ -0,0 +1,22 @@ +// Copyright 2020 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 "sort" + +// SliceEq return true if two slice have the same elements even if different sort. +func SliceEq(left, right []string) bool { + if len(left) != len(right) { + return false + } + sort.Sort(sort.StringSlice(left)) + sort.Sort(sort.StringSlice(right)) + for i := 0; i < len(left); i++ { + if left[i] != right[i] { + return false + } + } + return true +} diff --git a/session.go b/session.go index 8c692879..d4d9f78a 100644 --- a/session.go +++ b/session.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/core" "xorm.io/xorm/schemas" ) @@ -453,7 +454,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { + 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 @@ -465,12 +466,12 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } } - if _, ok := fieldValue.Interface().(Conversion); ok { + 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().(Conversion).FromDB(data) + fieldValue.Interface().(convert.Conversion).FromDB(data) } else { return nil, err } diff --git a/session_convert.go b/session_convert.go index 1c19092d..04436ec6 100644 --- a/session_convert.go +++ b/session_convert.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "xorm.io/xorm/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -91,11 +92,11 @@ var ( // 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().(Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { return structConvert.FromDB(data) } - if structConvert, ok := fieldValue.Interface().(Conversion); ok { + if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { return structConvert.FromDB(data) } @@ -539,7 +540,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val // convert a field value of a struct to interface for put into db func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect.Value) (interface{}, error) { if fieldValue.CanAddr() { - if fieldConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { + if fieldConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err @@ -551,7 +552,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. } } - if fieldConvert, ok := fieldValue.Interface().(Conversion); ok { + if fieldConvert, ok := fieldValue.Interface().(convert.Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err diff --git a/session_delete.go b/session_delete.go index a639d61f..6bcb3852 100644 --- a/session_delete.go +++ b/session_delete.go @@ -28,7 +28,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri return ErrCacheFailed } - cacher := session.engine.GetCacher(tableName) + cacher := session.engine.cacherMgr.GetCacher(tableName) pkColumns := table.PKColumns() ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { diff --git a/session_find.go b/session_find.go index fd1d49b1..492f19e6 100644 --- a/session_find.go +++ b/session_find.go @@ -329,7 +329,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } tableName := session.statement.TableName() - cacher := session.engine.GetCacher(tableName) + cacher := session.engine.cacherMgr.GetCacher(tableName) if cacher == nil { return nil } diff --git a/session_get.go b/session_get.go index bf91eacf..d1e96958 100644 --- a/session_get.go +++ b/session_get.go @@ -280,7 +280,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } tableName := session.statement.TableName() - cacher := session.engine.GetCacher(tableName) + cacher := session.engine.cacherMgr.GetCacher(tableName) session.engine.logger.Debug("[cache] Get SQL:", newsql, args) table := session.statement.RefTable diff --git a/session_insert.go b/session_insert.go index fcd7b2b5..4e822247 100644 --- a/session_insert.go +++ b/session_insert.go @@ -613,7 +613,7 @@ func (session *Session) cacheInsert(table string) error { if !session.statement.UseCache { return nil } - cacher := session.engine.GetCacher(table) + cacher := session.engine.cacherMgr.GetCacher(table) if cacher == nil { return nil } diff --git a/session_schema.go b/session_schema.go index 31b2ea42..809f158f 100644 --- a/session_schema.go +++ b/session_schema.go @@ -9,6 +9,7 @@ import ( "fmt" "strings" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -188,7 +189,7 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo } for _, index := range indexes { - if sliceEq(index.Cols, cols) { + if utils.SliceEq(index.Cols, cols) { if unique { return index.Type == schemas.UniqueType, nil } @@ -241,7 +242,7 @@ func (session *Session) Sync2(beans ...interface{}) error { for _, bean := range beans { v := rValue(bean) - table, err := engine.mapType(v) + table, err := engine.tagParser.MapType(v) if err != nil { return err } diff --git a/statement.go b/statement.go index 651ce175..d3048601 100644 --- a/statement.go +++ b/statement.go @@ -12,6 +12,7 @@ import ( "time" "xorm.io/builder" + "xorm.io/xorm/convert" "xorm.io/xorm/dialects" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" @@ -329,7 +330,7 @@ func (statement *Statement) buildUpdates(bean interface{}, var val interface{} if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { data, err := structConvert.ToDB() if err != nil { engine.logger.Error(err) @@ -340,7 +341,7 @@ func (statement *Statement) buildUpdates(bean interface{}, } } - if structConvert, ok := fieldValue.Interface().(Conversion); ok { + if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { data, err := structConvert.ToDB() if err != nil { engine.logger.Error(err) diff --git a/tag_cache_test.go b/tag_cache_test.go deleted file mode 100644 index 30e2c51a..00000000 --- a/tag_cache_test.go +++ /dev/null @@ -1,35 +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 ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCacheTag(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type CacheDomain struct { - Id int64 `xorm:"pk cache"` - Name string - } - - assert.NoError(t, testEngine.CreateTables(&CacheDomain{})) - assert.True(t, testEngine.GetCacher(testEngine.TableName(&CacheDomain{})) != nil) -} - -func TestNoCacheTag(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type NoCacheDomain struct { - Id int64 `xorm:"pk nocache"` - Name string - } - - assert.NoError(t, testEngine.CreateTables(&NoCacheDomain{})) - assert.True(t, testEngine.GetCacher(testEngine.TableName(&NoCacheDomain{})) == nil) -} diff --git a/tag_extends_test.go b/tag_extends_test.go deleted file mode 100644 index 80540b57..00000000 --- a/tag_extends_test.go +++ /dev/null @@ -1,608 +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 ( - "errors" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "xorm.io/xorm/schemas" -) - -type tempUser struct { - Id int64 - Username string -} - -type tempUser2 struct { - TempUser tempUser `xorm:"extends"` - Departname string -} - -type tempUser3 struct { - Temp *tempUser `xorm:"extends"` - Departname string -} - -type tempUser4 struct { - TempUser2 tempUser2 `xorm:"extends"` -} - -type Userinfo struct { - Uid int64 `xorm:"id pk not null autoincr"` - Username string `xorm:"unique"` - Departname string - Alias string `xorm:"-"` - Created time.Time - Detail Userdetail `xorm:"detail_id int(11)"` - Height float64 - Avatar []byte - IsMan bool -} - -type Userdetail struct { - Id int64 - Intro string `xorm:"text"` - Profile string `xorm:"varchar(2000)"` -} - -type UserAndDetail struct { - Userinfo `xorm:"extends"` - Userdetail `xorm:"extends"` -} - -func TestExtends(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(&tempUser2{}) - assert.NoError(t, err) - - err = testEngine.CreateTables(&tempUser2{}) - assert.NoError(t, err) - - tu := &tempUser2{tempUser{0, "extends"}, "dev depart"} - _, err = testEngine.Insert(tu) - assert.NoError(t, err) - - tu2 := &tempUser2{} - _, err = testEngine.Get(tu2) - assert.NoError(t, err) - - tu3 := &tempUser2{tempUser{0, "extends update"}, ""} - _, err = testEngine.ID(tu2.TempUser.Id).Update(tu3) - assert.NoError(t, err) - - err = testEngine.DropTables(&tempUser4{}) - assert.NoError(t, err) - - err = testEngine.CreateTables(&tempUser4{}) - assert.NoError(t, err) - - tu8 := &tempUser4{tempUser2{tempUser{0, "extends"}, "dev depart"}} - _, err = testEngine.Insert(tu8) - assert.NoError(t, err) - - tu9 := &tempUser4{} - _, err = testEngine.Get(tu9) - assert.NoError(t, err) - - if tu9.TempUser2.TempUser.Username != tu8.TempUser2.TempUser.Username || tu9.TempUser2.Departname != tu8.TempUser2.Departname { - err = errors.New(fmt.Sprintln("not equal for", tu8, tu9)) - t.Error(err) - panic(err) - } - - tu10 := &tempUser4{tempUser2{tempUser{0, "extends update"}, ""}} - _, err = testEngine.ID(tu9.TempUser2.TempUser.Id).Update(tu10) - assert.NoError(t, err) - - err = testEngine.DropTables(&tempUser3{}) - assert.NoError(t, err) - - err = testEngine.CreateTables(&tempUser3{}) - assert.NoError(t, err) - - tu4 := &tempUser3{&tempUser{0, "extends"}, "dev depart"} - _, err = testEngine.Insert(tu4) - assert.NoError(t, err) - - tu5 := &tempUser3{} - _, err = testEngine.Get(tu5) - assert.NoError(t, err) - - if tu5.Temp == nil { - err = errors.New("error get data extends") - t.Error(err) - panic(err) - } - if tu5.Temp.Id != 1 || tu5.Temp.Username != "extends" || - tu5.Departname != "dev depart" { - err = errors.New("error get data extends") - t.Error(err) - panic(err) - } - - tu6 := &tempUser3{&tempUser{0, "extends update"}, ""} - _, err = testEngine.ID(tu5.Temp.Id).Update(tu6) - assert.NoError(t, err) - - users := make([]tempUser3, 0) - err = testEngine.Find(&users) - assert.NoError(t, err) - assert.EqualValues(t, 1, len(users), "error get data not 1") - - assertSync(t, new(Userinfo), new(Userdetail)) - - detail := Userdetail{ - Intro: "I'm in China", - } - _, err = testEngine.Insert(&detail) - assert.NoError(t, err) - - _, err = testEngine.Insert(&Userinfo{ - Username: "lunny", - Detail: detail, - }) - assert.NoError(t, err) - - var info UserAndDetail - qt := testEngine.Quote - ui := testEngine.TableName(new(Userinfo), true) - ud := testEngine.TableName(&detail, true) - uiid := testEngine.GetColumnMapper().Obj2Table("Id") - udid := "detail_id" - sql := fmt.Sprintf("select * from %s, %s where %s.%s = %s.%s", - qt(ui), qt(ud), qt(ui), qt(udid), qt(ud), qt(uiid)) - b, err := testEngine.SQL(sql).NoCascade().Get(&info) - assert.NoError(t, err) - if !b { - err = errors.New("should has lest one record") - t.Error(err) - panic(err) - } - fmt.Println(info) - if info.Userinfo.Uid == 0 || info.Userdetail.Id == 0 { - err = errors.New("all of the id should has value") - t.Error(err) - panic(err) - } - - fmt.Println("----join--info2") - var info2 UserAndDetail - b, err = testEngine.Table(&Userinfo{}). - Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). - NoCascade().Get(&info2) - if err != nil { - t.Error(err) - panic(err) - } - if !b { - err = errors.New("should has lest one record") - t.Error(err) - panic(err) - } - if info2.Userinfo.Uid == 0 || info2.Userdetail.Id == 0 { - err = errors.New("all of the id should has value") - t.Error(err) - panic(err) - } - fmt.Println(info2) - - fmt.Println("----join--infos2") - var infos2 = make([]UserAndDetail, 0) - err = testEngine.Table(&Userinfo{}). - Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). - NoCascade(). - Find(&infos2) - assert.NoError(t, err) - fmt.Println(infos2) -} - -type MessageBase struct { - Id int64 `xorm:"int(11) pk autoincr"` - TypeId int64 `xorm:"int(11) notnull"` -} - -type Message struct { - MessageBase `xorm:"extends"` - Title string `xorm:"varchar(100) notnull"` - Content string `xorm:"text notnull"` - Uid int64 `xorm:"int(11) notnull"` - ToUid int64 `xorm:"int(11) notnull"` - CreateTime time.Time `xorm:"datetime notnull created"` -} - -type MessageUser struct { - Id int64 - Name string -} - -type MessageType struct { - Id int64 - Name string -} - -type MessageExtend3 struct { - Message `xorm:"extends"` - Sender MessageUser `xorm:"extends"` - Receiver MessageUser `xorm:"extends"` - Type MessageType `xorm:"extends"` -} - -type MessageExtend4 struct { - Message `xorm:"extends"` - MessageUser `xorm:"extends"` - MessageType `xorm:"extends"` -} - -func TestExtends2(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) - assert.NoError(t, err) - - 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"} - _, err = testEngine.Insert(&sender, &receiver, &msgtype) - assert.NoError(t, err) - - msg := Message{ - MessageBase: MessageBase{ - Id: msgtype.Id, - }, - Title: "test", - Content: "test", - Uid: sender.Id, - ToUid: receiver.Id, - } - - session := testEngine.NewSession() - defer session.Close() - - // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == schemas.MSSQL { - err = session.Begin() - assert.NoError(t, err) - _, err = session.Exec("SET IDENTITY_INSERT message ON") - assert.NoError(t, err) - } - cnt, err := session.Insert(&msg) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - if testEngine.Dialect().DBType() == schemas.MSSQL { - err = session.Commit() - assert.NoError(t, err) - } - - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote - userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) - typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) - msgTableName := quote(testEngine.TableName(mapper("Message"), true)) - - list := make([]Message, 0) - err = session.Table(msgTableName).Join("LEFT", []string{userTableName, "sender"}, "`sender`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). - Join("LEFT", []string{userTableName, "receiver"}, "`receiver`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("ToUid")+"`"). - Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). - Find(&list) - assert.NoError(t, err) - - assert.EqualValues(t, 1, len(list), fmt.Sprintln("should have 1 message, got", len(list))) - assert.EqualValues(t, msg.Id, list[0].Id, fmt.Sprintln("should message equal", list[0], msg)) -} - -func TestExtends3(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } - - var sender = MessageUser{Name: "sender"} - var receiver = MessageUser{Name: "receiver"} - var msgtype = MessageType{Name: "type"} - _, err = testEngine.Insert(&sender, &receiver, &msgtype) - if err != nil { - t.Error(err) - panic(err) - } - - msg := Message{ - MessageBase: MessageBase{ - Id: msgtype.Id, - }, - Title: "test", - Content: "test", - Uid: sender.Id, - ToUid: receiver.Id, - } - - session := testEngine.NewSession() - defer session.Close() - - // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == schemas.MSSQL { - err = session.Begin() - assert.NoError(t, err) - _, err = session.Exec("SET IDENTITY_INSERT message ON") - assert.NoError(t, err) - } - _, err = session.Insert(&msg) - assert.NoError(t, err) - - if testEngine.Dialect().DBType() == schemas.MSSQL { - err = session.Commit() - assert.NoError(t, err) - } - - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote - userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) - typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) - msgTableName := quote(testEngine.TableName(mapper("Message"), true)) - - list := make([]MessageExtend3, 0) - err = session.Table(msgTableName).Join("LEFT", []string{userTableName, "sender"}, "`sender`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). - Join("LEFT", []string{userTableName, "receiver"}, "`receiver`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("ToUid")+"`"). - Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). - Find(&list) - assert.NoError(t, err) - - if len(list) != 1 { - err = errors.New(fmt.Sprintln("should have 1 message, got", len(list))) - t.Error(err) - panic(err) - } - - if list[0].Message.Id != msg.Id { - err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg)) - t.Error(err) - panic(err) - } - - if list[0].Sender.Id != sender.Id || list[0].Sender.Name != sender.Name { - err = errors.New(fmt.Sprintln("should sender equal", list[0].Sender, sender)) - t.Error(err) - panic(err) - } - - if list[0].Receiver.Id != receiver.Id || list[0].Receiver.Name != receiver.Name { - err = errors.New(fmt.Sprintln("should receiver equal", list[0].Receiver, receiver)) - t.Error(err) - panic(err) - } - - if list[0].Type.Id != msgtype.Id || list[0].Type.Name != msgtype.Name { - err = errors.New(fmt.Sprintln("should msgtype equal", list[0].Type, msgtype)) - t.Error(err) - panic(err) - } -} - -func TestExtends4(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } - - var sender = MessageUser{Name: "sender"} - var msgtype = MessageType{Name: "type"} - _, err = testEngine.Insert(&sender, &msgtype) - if err != nil { - t.Error(err) - panic(err) - } - - msg := Message{ - MessageBase: MessageBase{ - Id: msgtype.Id, - }, - Title: "test", - Content: "test", - Uid: sender.Id, - } - - session := testEngine.NewSession() - defer session.Close() - - // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == schemas.MSSQL { - err = session.Begin() - assert.NoError(t, err) - _, err = session.Exec("SET IDENTITY_INSERT message ON") - assert.NoError(t, err) - } - _, err = session.Insert(&msg) - assert.NoError(t, err) - - if testEngine.Dialect().DBType() == schemas.MSSQL { - err = session.Commit() - assert.NoError(t, err) - } - - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote - userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) - typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) - msgTableName := quote(testEngine.TableName(mapper("Message"), true)) - - list := make([]MessageExtend4, 0) - err = session.Table(msgTableName).Join("LEFT", userTableName, userTableName+".`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). - Join("LEFT", typeTableName, typeTableName+".`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). - Find(&list) - if err != nil { - t.Error(err) - panic(err) - } - - if len(list) != 1 { - err = errors.New(fmt.Sprintln("should have 1 message, got", len(list))) - t.Error(err) - panic(err) - } - - if list[0].Message.Id != msg.Id { - err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg)) - t.Error(err) - panic(err) - } - - if list[0].MessageUser.Id != sender.Id || list[0].MessageUser.Name != sender.Name { - err = errors.New(fmt.Sprintln("should sender equal", list[0].MessageUser, sender)) - t.Error(err) - panic(err) - } - - if list[0].MessageType.Id != msgtype.Id || list[0].MessageType.Name != msgtype.Name { - err = errors.New(fmt.Sprintln("should msgtype equal", list[0].MessageType, msgtype)) - t.Error(err) - panic(err) - } -} - -type Size struct { - ID int64 `xorm:"int(4) 'id' pk autoincr"` - Width float32 `json:"width" xorm:"float 'Width'"` - Height float32 `json:"height" xorm:"float 'Height'"` -} - -type Book struct { - ID int64 `xorm:"int(4) 'id' pk autoincr"` - SizeOpen *Size `xorm:"extends('Open')"` - SizeClosed *Size `xorm:"extends('Closed')"` - Size *Size `xorm:"extends('')"` -} - -func TestExtends5(t *testing.T) { - assert.NoError(t, prepareEngine()) - err := testEngine.DropTables(&Book{}, &Size{}) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(&Size{}, &Book{}) - if err != nil { - t.Error(err) - panic(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{ - SizeOpen: &so, - SizeClosed: &sc, - Size: &s, - } - var bk2 = Book{ - SizeOpen: &so, - } - var bk3 = Book{ - SizeClosed: &sc, - Size: &s, - } - var bk4 = Book{} - var 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{ - bk1.ID: bk1, - bk2.ID: bk2, - bk3.ID: bk3, - bk4.ID: bk4, - bk5.ID: bk5, - } - - session := testEngine.NewSession() - defer session.Close() - - var mapper = testEngine.GetTableMapper().Obj2Table - var quote = testEngine.Quote - bookTableName := quote(testEngine.TableName(mapper("Book"), true)) - sizeTableName := quote(testEngine.TableName(mapper("Size"), true)) - - list := make([]Book, 0) - err = session. - Select(fmt.Sprintf( - "%s.%s, sc.%s AS %s, sc.%s AS %s, s.%s, s.%s", - quote(bookTableName), - quote("id"), - quote("Width"), - quote("ClosedWidth"), - quote("Height"), - quote("ClosedHeight"), - quote("Width"), - quote("Height"), - )). - Table(bookTableName). - Join( - "LEFT", - sizeTableName+" AS `sc`", - bookTableName+".`SizeClosed`=sc.`id`", - ). - Join( - "LEFT", - sizeTableName+" AS `s`", - bookTableName+".`Size`=s.`id`", - ). - Find(&list) - if err != nil { - t.Error(err) - panic(err) - } - - for _, book := range list { - if ok := assert.Equal(t, books[book.ID].SizeClosed.Width, book.SizeClosed.Width); !ok { - t.Error("Not bounded size closed") - panic("Not bounded size closed") - } - - if ok := assert.Equal(t, books[book.ID].SizeClosed.Height, book.SizeClosed.Height); !ok { - t.Error("Not bounded size closed") - panic("Not bounded size closed") - } - - if books[book.ID].Size != nil || book.Size != nil { - if ok := assert.Equal(t, books[book.ID].Size.Width, book.Size.Width); !ok { - t.Error("Not bounded size") - panic("Not bounded size") - } - - if ok := assert.Equal(t, books[book.ID].Size.Height, book.Size.Height); !ok { - t.Error("Not bounded size") - panic("Not bounded size") - } - } - } -} diff --git a/tag_id_test.go b/tag_id_test.go deleted file mode 100644 index a386b783..00000000 --- a/tag_id_test.go +++ /dev/null @@ -1,85 +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 ( - "testing" - - "github.com/stretchr/testify/assert" - "xorm.io/xorm/names" -) - -type IDGonicMapper struct { - ID int64 -} - -func TestGonicMapperID(t *testing.T) { - assert.NoError(t, prepareEngine()) - - oldMapper := testEngine.GetColumnMapper() - testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) - testEngine.SetMapper(names.LintGonicMapper) - defer func() { - testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) - testEngine.SetMapper(oldMapper) - }() - - err := testEngine.CreateTables(new(IDGonicMapper)) - if err != nil { - t.Fatal(err) - } - - tables, err := testEngine.DBMetas() - if err != nil { - t.Fatal(err) - } - - for _, tb := range tables { - if tb.Name == "id_gonic_mapper" { - if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "id" { - t.Fatal(tb) - } - return - } - } - - t.Fatal("not table id_gonic_mapper") -} - -type IDSameMapper struct { - ID int64 -} - -func TestSameMapperID(t *testing.T) { - assert.NoError(t, prepareEngine()) - - oldMapper := testEngine.GetColumnMapper() - testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) - testEngine.SetMapper(names.SameMapper{}) - defer func() { - testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) - testEngine.SetMapper(oldMapper) - }() - - err := testEngine.CreateTables(new(IDSameMapper)) - if err != nil { - t.Fatal(err) - } - - tables, err := testEngine.DBMetas() - if err != nil { - t.Fatal(err) - } - - for _, tb := range tables { - if tb.Name == "IDSameMapper" { - if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "ID" { - t.Fatalf("tb %s tb.PKColumns() is %d not 1, tb.PKColumns()[0].Name is %s not ID", tb.Name, len(tb.PKColumns()), tb.PKColumns()[0].Name) - } - return - } - } - t.Fatal("not table IDSameMapper") -} diff --git a/tag_test.go b/tag_test.go deleted file mode 100644 index 894addac..00000000 --- a/tag_test.go +++ /dev/null @@ -1,600 +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" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "xorm.io/xorm/schemas" -) - -type UserCU struct { - Id int64 - Name string - Created time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` -} - -func TestCreatedAndUpdated(t *testing.T) { - assert.NoError(t, prepareEngine()) - - u := new(UserCU) - err := testEngine.DropTables(u) - assert.NoError(t, err) - - err = testEngine.CreateTables(u) - assert.NoError(t, err) - - u.Name = "sss" - cnt, err := testEngine.Insert(u) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - u.Name = "xxx" - cnt, err = testEngine.ID(u.Id).Update(u) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - u.Id = 0 - u.Created = time.Now().Add(-time.Hour * 24 * 365) - u.Updated = u.Created - cnt, err = testEngine.NoAutoTime().Insert(u) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) -} - -type StrangeName struct { - Id_t int64 `xorm:"pk autoincr"` - Name string -} - -func TestStrangeName(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(new(StrangeName)) - assert.NoError(t, err) - - err = testEngine.CreateTables(new(StrangeName)) - assert.NoError(t, err) - - _, err = testEngine.Insert(&StrangeName{Name: "sfsfdsfds"}) - assert.NoError(t, err) - - beans := make([]StrangeName, 0) - err = testEngine.Find(&beans) - assert.NoError(t, err) -} - -func TestCreatedUpdated(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type CreatedUpdated struct { - Id int64 - Name string - Value float64 `xorm:"numeric"` - Created time.Time `xorm:"created"` - Created2 time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` - } - - err := testEngine.Sync2(&CreatedUpdated{}) - assert.NoError(t, err) - - c := &CreatedUpdated{Name: "test"} - _, err = testEngine.Insert(c) - assert.NoError(t, err) - - c2 := new(CreatedUpdated) - has, err := testEngine.ID(c.Id).Get(c2) - assert.NoError(t, err) - - assert.True(t, has) - - c2.Value -= 1 - _, err = testEngine.ID(c2.Id).Update(c2) - assert.NoError(t, err) -} - -func TestCreatedUpdatedInt64(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type CreatedUpdatedInt64 struct { - Id int64 - Name string - Value float64 `xorm:"numeric"` - Created int64 `xorm:"created"` - Created2 int64 `xorm:"created"` - Updated int64 `xorm:"updated"` - } - - assertSync(t, &CreatedUpdatedInt64{}) - - c := &CreatedUpdatedInt64{Name: "test"} - _, err := testEngine.Insert(c) - assert.NoError(t, err) - - c2 := new(CreatedUpdatedInt64) - has, err := testEngine.ID(c.Id).Get(c2) - assert.NoError(t, err) - assert.True(t, has) - - c2.Value -= 1 - _, err = testEngine.ID(c2.Id).Update(c2) - assert.NoError(t, err) -} - -type Lowercase struct { - Id int64 - Name string - ended int64 `xorm:"-"` -} - -func TestLowerCase(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.Sync2(&Lowercase{}) - assert.NoError(t, err) - _, err = testEngine.Where("id > 0").Delete(&Lowercase{}) - assert.NoError(t, err) - - _, err = testEngine.Insert(&Lowercase{ended: 1}) - assert.NoError(t, err) - - ls := make([]Lowercase, 0) - err = testEngine.Find(&ls) - assert.NoError(t, err) - assert.EqualValues(t, 1, len(ls)) -} - -func TestAutoIncrTag(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type TestAutoIncr1 struct { - Id int64 - } - - tb := testEngine.TableInfo(new(TestAutoIncr1)) - cols := tb.Columns() - assert.EqualValues(t, 1, len(cols)) - assert.True(t, cols[0].IsAutoIncrement) - assert.True(t, cols[0].IsPrimaryKey) - assert.Equal(t, "id", cols[0].Name) - - type TestAutoIncr2 struct { - Id int64 `xorm:"id"` - } - - tb = testEngine.TableInfo(new(TestAutoIncr2)) - 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) - - type TestAutoIncr3 struct { - Id int64 `xorm:"'ID'"` - } - - tb = testEngine.TableInfo(new(TestAutoIncr3)) - 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) - - type TestAutoIncr4 struct { - Id int64 `xorm:"pk"` - } - - tb = testEngine.TableInfo(new(TestAutoIncr4)) - cols = tb.Columns() - assert.EqualValues(t, 1, len(cols)) - assert.False(t, cols[0].IsAutoIncrement) - assert.True(t, cols[0].IsPrimaryKey) - assert.Equal(t, "id", cols[0].Name) -} - -func TestTagComment(t *testing.T) { - assert.NoError(t, prepareEngine()) - // FIXME: only support mysql - if testEngine.Dialect().DriverName() != schemas.MYSQL { - return - } - - type TestComment1 struct { - Id int64 `xorm:"comment(主键)"` - } - - assert.NoError(t, testEngine.Sync2(new(TestComment1))) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - assert.EqualValues(t, 1, len(tables)) - assert.EqualValues(t, 1, len(tables[0].Columns())) - assert.EqualValues(t, "主键", tables[0].Columns()[0].Comment) - - assert.NoError(t, testEngine.DropTables(new(TestComment1))) - - type TestComment2 struct { - Id int64 `xorm:"comment('主键')"` - } - - assert.NoError(t, testEngine.Sync2(new(TestComment2))) - - tables, err = testEngine.DBMetas() - assert.NoError(t, err) - assert.EqualValues(t, 1, len(tables)) - assert.EqualValues(t, 1, len(tables[0].Columns())) - assert.EqualValues(t, "主键", tables[0].Columns()[0].Comment) -} - -func TestTagDefault(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type DefaultStruct struct { - Id int64 - Name string - Age int `xorm:"default(10)"` - } - - assertSync(t, new(DefaultStruct)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - - var defaultVal string - var isDefaultExist bool - tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct") - for _, table := range tables { - if table.Name == tableName { - col := table.GetColumn("age") - assert.NotNil(t, col) - defaultVal = col.Default - isDefaultExist = !col.DefaultIsEmpty - break - } - } - assert.True(t, isDefaultExist) - assert.EqualValues(t, "10", defaultVal) - - cnt, err := testEngine.Omit("age").Insert(&DefaultStruct{ - Name: "test", - Age: 20, - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var s DefaultStruct - has, err := testEngine.ID(1).Get(&s) - assert.NoError(t, err) - assert.True(t, has) - assert.EqualValues(t, 10, s.Age) - assert.EqualValues(t, "test", s.Name) -} - -func TestTagDefault2(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type DefaultStruct2 struct { - Id int64 - Name string - } - - assertSync(t, new(DefaultStruct2)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - - var defaultVal string - var isDefaultExist bool - tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct2") - for _, table := range tables { - if table.Name == tableName { - col := table.GetColumn("name") - assert.NotNil(t, col) - defaultVal = col.Default - isDefaultExist = !col.DefaultIsEmpty - break - } - } - assert.False(t, isDefaultExist, fmt.Sprintf("default value is --%v--", defaultVal)) - assert.EqualValues(t, "", defaultVal) -} - -func TestTagDefault3(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type DefaultStruct3 struct { - Id int64 - Name string `xorm:"default('myname')"` - } - - assertSync(t, new(DefaultStruct3)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - - var defaultVal string - var isDefaultExist bool - tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct3") - for _, table := range tables { - if table.Name == tableName { - col := table.GetColumn("name") - assert.NotNil(t, col) - defaultVal = col.Default - isDefaultExist = !col.DefaultIsEmpty - break - } - } - assert.True(t, isDefaultExist) - assert.EqualValues(t, "'myname'", defaultVal) -} - -func TestTagDefault4(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type DefaultStruct4 struct { - Id int64 - Created time.Time `xorm:"default(CURRENT_TIMESTAMP)"` - } - - assertSync(t, new(DefaultStruct4)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - - var defaultVal string - var isDefaultExist bool - tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct4") - for _, table := range tables { - if table.Name == tableName { - col := table.GetColumn("created") - assert.NotNil(t, col) - defaultVal = col.Default - isDefaultExist = !col.DefaultIsEmpty - break - } - } - assert.True(t, isDefaultExist) - assert.True(t, "CURRENT_TIMESTAMP" == defaultVal || - "now()" == defaultVal || - "getdate" == defaultVal, defaultVal) -} - -func TestTagDefault5(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type DefaultStruct5 struct { - Id int64 - Created time.Time `xorm:"default('2006-01-02 15:04:05')"` - } - - assertSync(t, new(DefaultStruct5)) - table := testEngine.TableInfo(new(DefaultStruct5)) - createdCol := table.GetColumn("created") - assert.NotNil(t, createdCol) - assert.EqualValues(t, "'2006-01-02 15:04:05'", createdCol.Default) - assert.False(t, createdCol.DefaultIsEmpty) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - - var defaultVal string - var isDefaultExist bool - tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct5") - for _, table := range tables { - if table.Name == tableName { - col := table.GetColumn("created") - assert.NotNil(t, col) - defaultVal = col.Default - isDefaultExist = !col.DefaultIsEmpty - break - } - } - assert.True(t, isDefaultExist) - assert.EqualValues(t, "'2006-01-02 15:04:05'", defaultVal) -} - -func TestTagDefault6(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type DefaultStruct6 struct { - Id int64 - IsMan bool `xorm:"default(true)"` - } - - assertSync(t, new(DefaultStruct6)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - - var defaultVal string - var isDefaultExist bool - tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct6") - for _, table := range tables { - if table.Name == tableName { - col := table.GetColumn("is_man") - assert.NotNil(t, col) - defaultVal = col.Default - isDefaultExist = !col.DefaultIsEmpty - break - } - } - assert.True(t, isDefaultExist) - if defaultVal == "1" { - defaultVal = "true" - } else if defaultVal == "0" { - defaultVal = "false" - } - assert.EqualValues(t, "true", defaultVal) -} - -func TestTagsDirection(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type OnlyFromDBStruct struct { - Id int64 - Name string - Uuid string `xorm:"<- default '1'"` - } - - assertSync(t, new(OnlyFromDBStruct)) - - cnt, err := testEngine.Insert(&OnlyFromDBStruct{ - Name: "test", - Uuid: "2", - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var s OnlyFromDBStruct - has, err := testEngine.ID(1).Get(&s) - assert.NoError(t, err) - assert.True(t, has) - assert.EqualValues(t, "1", s.Uuid) - assert.EqualValues(t, "test", s.Name) - - cnt, err = testEngine.ID(1).Update(&OnlyFromDBStruct{ - Uuid: "3", - Name: "test1", - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var s3 OnlyFromDBStruct - has, err = testEngine.ID(1).Get(&s3) - assert.NoError(t, err) - assert.True(t, has) - assert.EqualValues(t, "1", s3.Uuid) - assert.EqualValues(t, "test1", s3.Name) - - type OnlyToDBStruct struct { - Id int64 - Name string - Uuid string `xorm:"->"` - } - - assertSync(t, new(OnlyToDBStruct)) - - cnt, err = testEngine.Insert(&OnlyToDBStruct{ - Name: "test", - Uuid: "2", - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var s2 OnlyToDBStruct - has, err = testEngine.ID(1).Get(&s2) - assert.NoError(t, err) - assert.True(t, has) - assert.EqualValues(t, "", s2.Uuid) - assert.EqualValues(t, "test", s2.Name) -} - -func TestTagTime(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type TagUTCStruct struct { - Id int64 - Name string - Created time.Time `xorm:"created utc"` - } - - assertSync(t, new(TagUTCStruct)) - - assert.EqualValues(t, time.Local.String(), testEngine.GetTZLocation().String()) - - s := TagUTCStruct{ - Name: "utc", - } - cnt, err := testEngine.Insert(&s) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var u TagUTCStruct - has, err := testEngine.ID(1).Get(&u) - assert.NoError(t, err) - assert.True(t, has) - assert.EqualValues(t, s.Created.Format("2006-01-02 15:04:05"), u.Created.Format("2006-01-02 15:04:05")) - - var tm string - has, err = testEngine.Table("tag_u_t_c_struct").Cols("created").Get(&tm) - 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)) -} - -func TestSplitTag(t *testing.T) { - var cases = []struct { - tag string - tags []string - }{ - {"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"}}, - } - - for _, kase := range cases { - tags := splitTag(kase.tag) - if !sliceEq(tags, kase.tags) { - t.Fatalf("[%d]%v is not equal [%d]%v", len(tags), tags, len(kase.tags), kase.tags) - } - } -} - -func TestTagAutoIncr(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type TagAutoIncr struct { - Id int64 - Name string - } - - assertSync(t, new(TagAutoIncr)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - assert.EqualValues(t, 1, len(tables)) - assert.EqualValues(t, tableMapper.Obj2Table("TagAutoIncr"), tables[0].Name) - col := tables[0].GetColumn(colMapper.Obj2Table("Id")) - assert.NotNil(t, col) - assert.True(t, col.IsPrimaryKey) - assert.True(t, col.IsAutoIncrement) - - col2 := tables[0].GetColumn(colMapper.Obj2Table("Name")) - assert.NotNil(t, col2) - assert.False(t, col2.IsPrimaryKey) - assert.False(t, col2.IsAutoIncrement) -} - -func TestTagPrimarykey(t *testing.T) { - assert.NoError(t, prepareEngine()) - type TagPrimaryKey struct { - Id int64 `xorm:"pk"` - Name string `xorm:"VARCHAR(20) pk"` - } - - assertSync(t, new(TagPrimaryKey)) - - tables, err := testEngine.DBMetas() - assert.NoError(t, err) - assert.EqualValues(t, 1, len(tables)) - assert.EqualValues(t, tableMapper.Obj2Table("TagPrimaryKey"), tables[0].Name) - col := tables[0].GetColumn(colMapper.Obj2Table("Id")) - assert.NotNil(t, col) - assert.True(t, col.IsPrimaryKey) - assert.False(t, col.IsAutoIncrement) - - col2 := tables[0].GetColumn(colMapper.Obj2Table("Name")) - assert.NotNil(t, col2) - assert.True(t, col2.IsPrimaryKey) - assert.False(t, col2.IsAutoIncrement) -} diff --git a/tag_version_test.go b/tag_version_test.go deleted file mode 100644 index 878737c1..00000000 --- a/tag_version_test.go +++ /dev/null @@ -1,242 +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 ( - "errors" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -type VersionS struct { - Id int64 - Name string - Ver int `xorm:"version"` - Created time.Time `xorm:"created"` -} - -func TestVersion1(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } - - ver := &VersionS{Name: "sfsfdsfds"} - _, err = testEngine.Insert(ver) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(ver) - if ver.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - newVer := new(VersionS) - has, err := testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - t.Error(fmt.Errorf("no version id is %v", ver.Id)) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - newVer.Name = "-------" - _, err = testEngine.ID(ver.Id).Update(newVer) - if err != nil { - t.Error(err) - panic(err) - } - if newVer.Ver != 2 { - err = errors.New("update should set version back to struct") - t.Error(err) - } - - newVer = new(VersionS) - has, err = testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 2 { - err = errors.New("update error") - t.Error(err) - panic(err) - } -} - -func TestVersion2(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } - - var vers = []VersionS{ - {Name: "sfsfdsfds"}, - {Name: "xxxxx"}, - } - _, err = testEngine.Insert(vers) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(vers) - - for _, v := range vers { - if v.Ver != 1 { - err := errors.New("version should be 1") - t.Error(err) - panic(err) - } - } -} - -type VersionUintS struct { - Id int64 - Name string - Ver uint `xorm:"version"` - Created time.Time `xorm:"created"` -} - -func TestVersion3(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } - - ver := &VersionUintS{Name: "sfsfdsfds"} - _, err = testEngine.Insert(ver) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(ver) - if ver.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - newVer := new(VersionUintS) - has, err := testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - t.Error(fmt.Errorf("no version id is %v", ver.Id)) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } - - newVer.Name = "-------" - _, err = testEngine.ID(ver.Id).Update(newVer) - if err != nil { - t.Error(err) - panic(err) - } - if newVer.Ver != 2 { - err = errors.New("update should set version back to struct") - t.Error(err) - } - - newVer = new(VersionUintS) - has, err = testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 2 { - err = errors.New("update error") - t.Error(err) - panic(err) - } -} - -func TestVersion4(t *testing.T) { - assert.NoError(t, prepareEngine()) - - err := testEngine.DropTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } - - err = testEngine.CreateTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } - - var vers = []VersionUintS{ - {Name: "sfsfdsfds"}, - {Name: "xxxxx"}, - } - _, err = testEngine.Insert(vers) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(vers) - - for _, v := range vers { - if v.Ver != 1 { - err := errors.New("version should be 1") - t.Error(err) - panic(err) - } - } -} diff --git a/tags/parser.go b/tags/parser.go new file mode 100644 index 00000000..15dcaa30 --- /dev/null +++ b/tags/parser.go @@ -0,0 +1,239 @@ +// Copyright 2020 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 tags + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" + + "xorm.io/xorm/caches" + "xorm.io/xorm/convert" + "xorm.io/xorm/dialects" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" +) + +type Parser struct { + identifier string + dialect dialects.Dialect + ColumnMapper names.Mapper + TableMapper names.Mapper + handlers map[string]Handler + cacherMgr *caches.Manager +} + +func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnMapper names.Mapper, cacherMgr *caches.Manager) *Parser { + return &Parser{ + identifier: identifier, + dialect: dialect, + TableMapper: tableMapper, + ColumnMapper: columnMapper, + handlers: defaultTagHandlers, + cacherMgr: cacherMgr, + } +} + +func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) { + if index, ok := table.Indexes[indexName]; ok { + index.AddColumn(col.Name) + col.Indexes[index.Name] = indexType + } else { + index := schemas.NewIndex(indexName, indexType) + index.AddColumn(col.Name) + table.AddIndex(index) + col.Indexes[index.Name] = indexType + } +} + +func (parser *Parser) MapType(v reflect.Value) (*schemas.Table, error) { + t := v.Type() + table := schemas.NewEmptyTable() + table.Type = t + table.Name = names.GetTableName(parser.TableMapper, v) + + var idFieldColName string + var hasCacheTag, hasNoCacheTag bool + + for i := 0; i < t.NumField(); i++ { + tag := t.Field(i).Tag + + ormTagStr := tag.Get(parser.identifier) + var col *schemas.Column + fieldValue := v.Field(i) + fieldType := fieldValue.Type() + + 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 + } + + 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/tag.go b/tags/tag.go similarity index 83% rename from tag.go rename to tags/tag.go index 92fcdb5e..3222615a 100644 --- a/tag.go +++ b/tags/tag.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package tags import ( "fmt" @@ -34,8 +34,8 @@ func splitTag(tag string) (tags []string) { return } -// tagContext represents a context for xorm tag parse. -type tagContext struct { +// Context represents a context for xorm tag parse. +type Context struct { tagName string params []string preTag, nextTag string @@ -45,18 +45,18 @@ type tagContext struct { isIndex bool isUnique bool indexNames map[string]int - engine *Engine + parser *Parser hasCacheTag bool hasNoCacheTag bool ignoreNext bool } -// tagHandler describes tag handler for XORM -type tagHandler func(ctx *tagContext) error +// Handler describes tag handler for XORM +type Handler func(ctx *Context) error var ( // defaultTagHandlers enumerates all the default tag handler - defaultTagHandlers = map[string]tagHandler{ + defaultTagHandlers = map[string]Handler{ "<-": OnlyFromDBTagHandler, "->": OnlyToDBTagHandler, "PK": PKTagHandler, @@ -86,43 +86,43 @@ func init() { } // IgnoreTagHandler describes ignored tag handler -func IgnoreTagHandler(ctx *tagContext) error { +func IgnoreTagHandler(ctx *Context) error { return nil } // OnlyFromDBTagHandler describes mapping direction tag handler -func OnlyFromDBTagHandler(ctx *tagContext) error { +func OnlyFromDBTagHandler(ctx *Context) error { ctx.col.MapType = schemas.ONLYFROMDB return nil } // OnlyToDBTagHandler describes mapping direction tag handler -func OnlyToDBTagHandler(ctx *tagContext) error { +func OnlyToDBTagHandler(ctx *Context) error { ctx.col.MapType = schemas.ONLYTODB return nil } // PKTagHandler describes primary key tag handler -func PKTagHandler(ctx *tagContext) error { +func PKTagHandler(ctx *Context) error { ctx.col.IsPrimaryKey = true ctx.col.Nullable = false return nil } // NULLTagHandler describes null tag handler -func NULLTagHandler(ctx *tagContext) error { +func NULLTagHandler(ctx *Context) error { ctx.col.Nullable = (strings.ToUpper(ctx.preTag) != "NOT") return nil } // NotNullTagHandler describes notnull tag handler -func NotNullTagHandler(ctx *tagContext) error { +func NotNullTagHandler(ctx *Context) error { ctx.col.Nullable = false return nil } // AutoIncrTagHandler describes autoincr tag handler -func AutoIncrTagHandler(ctx *tagContext) error { +func AutoIncrTagHandler(ctx *Context) error { ctx.col.IsAutoIncrement = true /* if len(ctx.params) > 0 { @@ -139,7 +139,7 @@ func AutoIncrTagHandler(ctx *tagContext) error { } // DefaultTagHandler describes default tag handler -func DefaultTagHandler(ctx *tagContext) error { +func DefaultTagHandler(ctx *Context) error { if len(ctx.params) > 0 { ctx.col.Default = ctx.params[0] } else { @@ -151,26 +151,26 @@ func DefaultTagHandler(ctx *tagContext) error { } // CreatedTagHandler describes created tag handler -func CreatedTagHandler(ctx *tagContext) error { +func CreatedTagHandler(ctx *Context) error { ctx.col.IsCreated = true return nil } // VersionTagHandler describes version tag handler -func VersionTagHandler(ctx *tagContext) error { +func VersionTagHandler(ctx *Context) error { ctx.col.IsVersion = true ctx.col.Default = "1" return nil } // UTCTagHandler describes utc tag handler -func UTCTagHandler(ctx *tagContext) error { +func UTCTagHandler(ctx *Context) error { ctx.col.TimeZone = time.UTC return nil } // LocalTagHandler describes local tag handler -func LocalTagHandler(ctx *tagContext) error { +func LocalTagHandler(ctx *Context) error { if len(ctx.params) == 0 { ctx.col.TimeZone = time.Local } else { @@ -184,19 +184,19 @@ func LocalTagHandler(ctx *tagContext) error { } // UpdatedTagHandler describes updated tag handler -func UpdatedTagHandler(ctx *tagContext) error { +func UpdatedTagHandler(ctx *Context) error { ctx.col.IsUpdated = true return nil } // DeletedTagHandler describes deleted tag handler -func DeletedTagHandler(ctx *tagContext) error { +func DeletedTagHandler(ctx *Context) error { ctx.col.IsDeleted = true return nil } // IndexTagHandler describes index tag handler -func IndexTagHandler(ctx *tagContext) error { +func IndexTagHandler(ctx *Context) error { if len(ctx.params) > 0 { ctx.indexNames[ctx.params[0]] = schemas.IndexType } else { @@ -206,7 +206,7 @@ func IndexTagHandler(ctx *tagContext) error { } // UniqueTagHandler describes unique tag handler -func UniqueTagHandler(ctx *tagContext) error { +func UniqueTagHandler(ctx *Context) error { if len(ctx.params) > 0 { ctx.indexNames[ctx.params[0]] = schemas.UniqueType } else { @@ -216,7 +216,7 @@ func UniqueTagHandler(ctx *tagContext) error { } // CommentTagHandler add comment to column -func CommentTagHandler(ctx *tagContext) error { +func CommentTagHandler(ctx *Context) error { if len(ctx.params) > 0 { ctx.col.Comment = strings.Trim(ctx.params[0], "' ") } @@ -224,7 +224,7 @@ func CommentTagHandler(ctx *tagContext) error { } // SQLTypeTagHandler describes SQL Type tag handler -func SQLTypeTagHandler(ctx *tagContext) error { +func SQLTypeTagHandler(ctx *Context) error { ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName} if len(ctx.params) > 0 { if ctx.tagName == schemas.Enum { @@ -264,7 +264,7 @@ func SQLTypeTagHandler(ctx *tagContext) error { } // ExtendsTagHandler describes extends tag handler -func ExtendsTagHandler(ctx *tagContext) error { +func ExtendsTagHandler(ctx *Context) error { var fieldValue = ctx.fieldValue var isPtr = false switch fieldValue.Kind() { @@ -280,7 +280,7 @@ func ExtendsTagHandler(ctx *tagContext) error { isPtr = true fallthrough case reflect.Struct: - parentTable, err := ctx.engine.mapType(fieldValue) + parentTable, err := ctx.parser.MapType(fieldValue) if err != nil { return err } @@ -316,7 +316,7 @@ func ExtendsTagHandler(ctx *tagContext) error { } // CacheTagHandler describes cache tag handler -func CacheTagHandler(ctx *tagContext) error { +func CacheTagHandler(ctx *Context) error { if !ctx.hasCacheTag { ctx.hasCacheTag = true } @@ -324,7 +324,7 @@ func CacheTagHandler(ctx *tagContext) error { } // NoCacheTagHandler describes nocache tag handler -func NoCacheTagHandler(ctx *tagContext) error { +func NoCacheTagHandler(ctx *Context) error { if !ctx.hasNoCacheTag { ctx.hasNoCacheTag = true } diff --git a/tags/tag_test.go b/tags/tag_test.go new file mode 100644 index 00000000..5775b40a --- /dev/null +++ b/tags/tag_test.go @@ -0,0 +1,30 @@ +// 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 tags + +import ( + "testing" + + "xorm.io/xorm/internal/utils" +) + +func TestSplitTag(t *testing.T) { + var cases = []struct { + tag string + tags []string + }{ + {"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"}}, + } + + 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) + } + } +} diff --git a/tags_test.go b/tags_test.go new file mode 100644 index 00000000..2d90948b --- /dev/null +++ b/tags_test.go @@ -0,0 +1,1501 @@ +// 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 ( + "errors" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" +) + +type tempUser struct { + Id int64 + Username string +} + +type tempUser2 struct { + TempUser tempUser `xorm:"extends"` + Departname string +} + +type tempUser3 struct { + Temp *tempUser `xorm:"extends"` + Departname string +} + +type tempUser4 struct { + TempUser2 tempUser2 `xorm:"extends"` +} + +type Userinfo struct { + Uid int64 `xorm:"id pk not null autoincr"` + Username string `xorm:"unique"` + Departname string + Alias string `xorm:"-"` + Created time.Time + Detail Userdetail `xorm:"detail_id int(11)"` + Height float64 + Avatar []byte + IsMan bool +} + +type Userdetail struct { + Id int64 + Intro string `xorm:"text"` + Profile string `xorm:"varchar(2000)"` +} + +type UserAndDetail struct { + Userinfo `xorm:"extends"` + Userdetail `xorm:"extends"` +} + +func TestExtends(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(&tempUser2{}) + assert.NoError(t, err) + + err = testEngine.CreateTables(&tempUser2{}) + assert.NoError(t, err) + + tu := &tempUser2{tempUser{0, "extends"}, "dev depart"} + _, err = testEngine.Insert(tu) + assert.NoError(t, err) + + tu2 := &tempUser2{} + _, err = testEngine.Get(tu2) + assert.NoError(t, err) + + tu3 := &tempUser2{tempUser{0, "extends update"}, ""} + _, err = testEngine.ID(tu2.TempUser.Id).Update(tu3) + assert.NoError(t, err) + + err = testEngine.DropTables(&tempUser4{}) + assert.NoError(t, err) + + err = testEngine.CreateTables(&tempUser4{}) + assert.NoError(t, err) + + tu8 := &tempUser4{tempUser2{tempUser{0, "extends"}, "dev depart"}} + _, err = testEngine.Insert(tu8) + assert.NoError(t, err) + + tu9 := &tempUser4{} + _, err = testEngine.Get(tu9) + assert.NoError(t, err) + + if tu9.TempUser2.TempUser.Username != tu8.TempUser2.TempUser.Username || tu9.TempUser2.Departname != tu8.TempUser2.Departname { + err = errors.New(fmt.Sprintln("not equal for", tu8, tu9)) + t.Error(err) + panic(err) + } + + tu10 := &tempUser4{tempUser2{tempUser{0, "extends update"}, ""}} + _, err = testEngine.ID(tu9.TempUser2.TempUser.Id).Update(tu10) + assert.NoError(t, err) + + err = testEngine.DropTables(&tempUser3{}) + assert.NoError(t, err) + + err = testEngine.CreateTables(&tempUser3{}) + assert.NoError(t, err) + + tu4 := &tempUser3{&tempUser{0, "extends"}, "dev depart"} + _, err = testEngine.Insert(tu4) + assert.NoError(t, err) + + tu5 := &tempUser3{} + _, err = testEngine.Get(tu5) + assert.NoError(t, err) + + if tu5.Temp == nil { + err = errors.New("error get data extends") + t.Error(err) + panic(err) + } + if tu5.Temp.Id != 1 || tu5.Temp.Username != "extends" || + tu5.Departname != "dev depart" { + err = errors.New("error get data extends") + t.Error(err) + panic(err) + } + + tu6 := &tempUser3{&tempUser{0, "extends update"}, ""} + _, err = testEngine.ID(tu5.Temp.Id).Update(tu6) + assert.NoError(t, err) + + users := make([]tempUser3, 0) + err = testEngine.Find(&users) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(users), "error get data not 1") + + assertSync(t, new(Userinfo), new(Userdetail)) + + detail := Userdetail{ + Intro: "I'm in China", + } + _, err = testEngine.Insert(&detail) + assert.NoError(t, err) + + _, err = testEngine.Insert(&Userinfo{ + Username: "lunny", + Detail: detail, + }) + assert.NoError(t, err) + + var info UserAndDetail + qt := testEngine.Quote + ui := testEngine.TableName(new(Userinfo), true) + ud := testEngine.TableName(&detail, true) + uiid := testEngine.GetColumnMapper().Obj2Table("Id") + udid := "detail_id" + sql := fmt.Sprintf("select * from %s, %s where %s.%s = %s.%s", + qt(ui), qt(ud), qt(ui), qt(udid), qt(ud), qt(uiid)) + b, err := testEngine.SQL(sql).NoCascade().Get(&info) + assert.NoError(t, err) + if !b { + err = errors.New("should has lest one record") + t.Error(err) + panic(err) + } + fmt.Println(info) + if info.Userinfo.Uid == 0 || info.Userdetail.Id == 0 { + err = errors.New("all of the id should has value") + t.Error(err) + panic(err) + } + + fmt.Println("----join--info2") + var info2 UserAndDetail + b, err = testEngine.Table(&Userinfo{}). + Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). + NoCascade().Get(&info2) + if err != nil { + t.Error(err) + panic(err) + } + if !b { + err = errors.New("should has lest one record") + t.Error(err) + panic(err) + } + if info2.Userinfo.Uid == 0 || info2.Userdetail.Id == 0 { + err = errors.New("all of the id should has value") + t.Error(err) + panic(err) + } + fmt.Println(info2) + + fmt.Println("----join--infos2") + var infos2 = make([]UserAndDetail, 0) + err = testEngine.Table(&Userinfo{}). + Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). + NoCascade(). + Find(&infos2) + assert.NoError(t, err) + fmt.Println(infos2) +} + +type MessageBase struct { + Id int64 `xorm:"int(11) pk autoincr"` + TypeId int64 `xorm:"int(11) notnull"` +} + +type Message struct { + MessageBase `xorm:"extends"` + Title string `xorm:"varchar(100) notnull"` + Content string `xorm:"text notnull"` + Uid int64 `xorm:"int(11) notnull"` + ToUid int64 `xorm:"int(11) notnull"` + CreateTime time.Time `xorm:"datetime notnull created"` +} + +type MessageUser struct { + Id int64 + Name string +} + +type MessageType struct { + Id int64 + Name string +} + +type MessageExtend3 struct { + Message `xorm:"extends"` + Sender MessageUser `xorm:"extends"` + Receiver MessageUser `xorm:"extends"` + Type MessageType `xorm:"extends"` +} + +type MessageExtend4 struct { + Message `xorm:"extends"` + MessageUser `xorm:"extends"` + MessageType `xorm:"extends"` +} + +func TestExtends2(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) + assert.NoError(t, err) + + 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"} + _, err = testEngine.Insert(&sender, &receiver, &msgtype) + assert.NoError(t, err) + + msg := Message{ + MessageBase: MessageBase{ + Id: msgtype.Id, + }, + Title: "test", + Content: "test", + Uid: sender.Id, + ToUid: receiver.Id, + } + + session := testEngine.NewSession() + defer session.Close() + + // MSSQL deny insert identity column excep declare as below + if testEngine.Dialect().DBType() == schemas.MSSQL { + err = session.Begin() + assert.NoError(t, err) + _, err = session.Exec("SET IDENTITY_INSERT message ON") + assert.NoError(t, err) + } + cnt, err := session.Insert(&msg) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + if testEngine.Dialect().DBType() == schemas.MSSQL { + err = session.Commit() + assert.NoError(t, err) + } + + var mapper = testEngine.GetTableMapper().Obj2Table + var quote = testEngine.Quote + userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) + typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) + msgTableName := quote(testEngine.TableName(mapper("Message"), true)) + + list := make([]Message, 0) + err = session.Table(msgTableName).Join("LEFT", []string{userTableName, "sender"}, "`sender`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). + Join("LEFT", []string{userTableName, "receiver"}, "`receiver`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("ToUid")+"`"). + Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). + Find(&list) + assert.NoError(t, err) + + assert.EqualValues(t, 1, len(list), fmt.Sprintln("should have 1 message, got", len(list))) + assert.EqualValues(t, msg.Id, list[0].Id, fmt.Sprintln("should message equal", list[0], msg)) +} + +func TestExtends3(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) + if err != nil { + t.Error(err) + panic(err) + } + + var sender = MessageUser{Name: "sender"} + var receiver = MessageUser{Name: "receiver"} + var msgtype = MessageType{Name: "type"} + _, err = testEngine.Insert(&sender, &receiver, &msgtype) + if err != nil { + t.Error(err) + panic(err) + } + + msg := Message{ + MessageBase: MessageBase{ + Id: msgtype.Id, + }, + Title: "test", + Content: "test", + Uid: sender.Id, + ToUid: receiver.Id, + } + + session := testEngine.NewSession() + defer session.Close() + + // MSSQL deny insert identity column excep declare as below + if testEngine.Dialect().DBType() == schemas.MSSQL { + err = session.Begin() + assert.NoError(t, err) + _, err = session.Exec("SET IDENTITY_INSERT message ON") + assert.NoError(t, err) + } + _, err = session.Insert(&msg) + assert.NoError(t, err) + + if testEngine.Dialect().DBType() == schemas.MSSQL { + err = session.Commit() + assert.NoError(t, err) + } + + var mapper = testEngine.GetTableMapper().Obj2Table + var quote = testEngine.Quote + userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) + typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) + msgTableName := quote(testEngine.TableName(mapper("Message"), true)) + + list := make([]MessageExtend3, 0) + err = session.Table(msgTableName).Join("LEFT", []string{userTableName, "sender"}, "`sender`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). + Join("LEFT", []string{userTableName, "receiver"}, "`receiver`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("ToUid")+"`"). + Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). + Find(&list) + assert.NoError(t, err) + + if len(list) != 1 { + err = errors.New(fmt.Sprintln("should have 1 message, got", len(list))) + t.Error(err) + panic(err) + } + + if list[0].Message.Id != msg.Id { + err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg)) + t.Error(err) + panic(err) + } + + if list[0].Sender.Id != sender.Id || list[0].Sender.Name != sender.Name { + err = errors.New(fmt.Sprintln("should sender equal", list[0].Sender, sender)) + t.Error(err) + panic(err) + } + + if list[0].Receiver.Id != receiver.Id || list[0].Receiver.Name != receiver.Name { + err = errors.New(fmt.Sprintln("should receiver equal", list[0].Receiver, receiver)) + t.Error(err) + panic(err) + } + + if list[0].Type.Id != msgtype.Id || list[0].Type.Name != msgtype.Name { + err = errors.New(fmt.Sprintln("should msgtype equal", list[0].Type, msgtype)) + t.Error(err) + panic(err) + } +} + +func TestExtends4(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) + if err != nil { + t.Error(err) + panic(err) + } + + var sender = MessageUser{Name: "sender"} + var msgtype = MessageType{Name: "type"} + _, err = testEngine.Insert(&sender, &msgtype) + if err != nil { + t.Error(err) + panic(err) + } + + msg := Message{ + MessageBase: MessageBase{ + Id: msgtype.Id, + }, + Title: "test", + Content: "test", + Uid: sender.Id, + } + + session := testEngine.NewSession() + defer session.Close() + + // MSSQL deny insert identity column excep declare as below + if testEngine.Dialect().DBType() == schemas.MSSQL { + err = session.Begin() + assert.NoError(t, err) + _, err = session.Exec("SET IDENTITY_INSERT message ON") + assert.NoError(t, err) + } + _, err = session.Insert(&msg) + assert.NoError(t, err) + + if testEngine.Dialect().DBType() == schemas.MSSQL { + err = session.Commit() + assert.NoError(t, err) + } + + var mapper = testEngine.GetTableMapper().Obj2Table + var quote = testEngine.Quote + userTableName := quote(testEngine.TableName(mapper("MessageUser"), true)) + typeTableName := quote(testEngine.TableName(mapper("MessageType"), true)) + msgTableName := quote(testEngine.TableName(mapper("Message"), true)) + + list := make([]MessageExtend4, 0) + err = session.Table(msgTableName).Join("LEFT", userTableName, userTableName+".`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). + Join("LEFT", typeTableName, typeTableName+".`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). + Find(&list) + if err != nil { + t.Error(err) + panic(err) + } + + if len(list) != 1 { + err = errors.New(fmt.Sprintln("should have 1 message, got", len(list))) + t.Error(err) + panic(err) + } + + if list[0].Message.Id != msg.Id { + err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg)) + t.Error(err) + panic(err) + } + + if list[0].MessageUser.Id != sender.Id || list[0].MessageUser.Name != sender.Name { + err = errors.New(fmt.Sprintln("should sender equal", list[0].MessageUser, sender)) + t.Error(err) + panic(err) + } + + if list[0].MessageType.Id != msgtype.Id || list[0].MessageType.Name != msgtype.Name { + err = errors.New(fmt.Sprintln("should msgtype equal", list[0].MessageType, msgtype)) + t.Error(err) + panic(err) + } +} + +type Size struct { + ID int64 `xorm:"int(4) 'id' pk autoincr"` + Width float32 `json:"width" xorm:"float 'Width'"` + Height float32 `json:"height" xorm:"float 'Height'"` +} + +type Book struct { + ID int64 `xorm:"int(4) 'id' pk autoincr"` + SizeOpen *Size `xorm:"extends('Open')"` + SizeClosed *Size `xorm:"extends('Closed')"` + Size *Size `xorm:"extends('')"` +} + +func TestExtends5(t *testing.T) { + assert.NoError(t, prepareEngine()) + err := testEngine.DropTables(&Book{}, &Size{}) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(&Size{}, &Book{}) + if err != nil { + t.Error(err) + panic(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{ + SizeOpen: &so, + SizeClosed: &sc, + Size: &s, + } + var bk2 = Book{ + SizeOpen: &so, + } + var bk3 = Book{ + SizeClosed: &sc, + Size: &s, + } + var bk4 = Book{} + var 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{ + bk1.ID: bk1, + bk2.ID: bk2, + bk3.ID: bk3, + bk4.ID: bk4, + bk5.ID: bk5, + } + + session := testEngine.NewSession() + defer session.Close() + + var mapper = testEngine.GetTableMapper().Obj2Table + var quote = testEngine.Quote + bookTableName := quote(testEngine.TableName(mapper("Book"), true)) + sizeTableName := quote(testEngine.TableName(mapper("Size"), true)) + + list := make([]Book, 0) + err = session. + Select(fmt.Sprintf( + "%s.%s, sc.%s AS %s, sc.%s AS %s, s.%s, s.%s", + quote(bookTableName), + quote("id"), + quote("Width"), + quote("ClosedWidth"), + quote("Height"), + quote("ClosedHeight"), + quote("Width"), + quote("Height"), + )). + Table(bookTableName). + Join( + "LEFT", + sizeTableName+" AS `sc`", + bookTableName+".`SizeClosed`=sc.`id`", + ). + Join( + "LEFT", + sizeTableName+" AS `s`", + bookTableName+".`Size`=s.`id`", + ). + Find(&list) + if err != nil { + t.Error(err) + panic(err) + } + + for _, book := range list { + if ok := assert.Equal(t, books[book.ID].SizeClosed.Width, book.SizeClosed.Width); !ok { + t.Error("Not bounded size closed") + panic("Not bounded size closed") + } + + if ok := assert.Equal(t, books[book.ID].SizeClosed.Height, book.SizeClosed.Height); !ok { + t.Error("Not bounded size closed") + panic("Not bounded size closed") + } + + if books[book.ID].Size != nil || book.Size != nil { + if ok := assert.Equal(t, books[book.ID].Size.Width, book.Size.Width); !ok { + t.Error("Not bounded size") + panic("Not bounded size") + } + + if ok := assert.Equal(t, books[book.ID].Size.Height, book.Size.Height); !ok { + t.Error("Not bounded size") + panic("Not bounded size") + } + } + } +} + +func TestCacheTag(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type CacheDomain struct { + Id int64 `xorm:"pk cache"` + Name string + } + + assert.NoError(t, testEngine.CreateTables(&CacheDomain{})) + assert.True(t, testEngine.GetCacher(testEngine.TableName(&CacheDomain{})) != nil) +} + +func TestNoCacheTag(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type NoCacheDomain struct { + Id int64 `xorm:"pk nocache"` + Name string + } + + assert.NoError(t, testEngine.CreateTables(&NoCacheDomain{})) + assert.True(t, testEngine.GetCacher(testEngine.TableName(&NoCacheDomain{})) == nil) +} + +type IDGonicMapper struct { + ID int64 +} + +func TestGonicMapperID(t *testing.T) { + assert.NoError(t, prepareEngine()) + + oldMapper := testEngine.GetColumnMapper() + testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) + testEngine.SetMapper(names.LintGonicMapper) + defer func() { + testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) + testEngine.SetMapper(oldMapper) + }() + + err := testEngine.CreateTables(new(IDGonicMapper)) + if err != nil { + t.Fatal(err) + } + + tables, err := testEngine.DBMetas() + if err != nil { + t.Fatal(err) + } + + for _, tb := range tables { + if tb.Name == "id_gonic_mapper" { + if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "id" { + t.Fatal(tb) + } + return + } + } + + t.Fatal("not table id_gonic_mapper") +} + +type IDSameMapper struct { + ID int64 +} + +func TestSameMapperID(t *testing.T) { + assert.NoError(t, prepareEngine()) + + oldMapper := testEngine.GetColumnMapper() + testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) + testEngine.SetMapper(names.SameMapper{}) + defer func() { + testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) + testEngine.SetMapper(oldMapper) + }() + + err := testEngine.CreateTables(new(IDSameMapper)) + if err != nil { + t.Fatal(err) + } + + tables, err := testEngine.DBMetas() + if err != nil { + t.Fatal(err) + } + + for _, tb := range tables { + if tb.Name == "IDSameMapper" { + if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "ID" { + t.Fatalf("tb %s tb.PKColumns() is %d not 1, tb.PKColumns()[0].Name is %s not ID", tb.Name, len(tb.PKColumns()), tb.PKColumns()[0].Name) + } + return + } + } + t.Fatal("not table IDSameMapper") +} + +type UserCU struct { + Id int64 + Name string + Created time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` +} + +func TestCreatedAndUpdated(t *testing.T) { + assert.NoError(t, prepareEngine()) + + u := new(UserCU) + err := testEngine.DropTables(u) + assert.NoError(t, err) + + err = testEngine.CreateTables(u) + assert.NoError(t, err) + + u.Name = "sss" + cnt, err := testEngine.Insert(u) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + u.Name = "xxx" + cnt, err = testEngine.ID(u.Id).Update(u) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + u.Id = 0 + u.Created = time.Now().Add(-time.Hour * 24 * 365) + u.Updated = u.Created + cnt, err = testEngine.NoAutoTime().Insert(u) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) +} + +type StrangeName struct { + Id_t int64 `xorm:"pk autoincr"` + Name string +} + +func TestStrangeName(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(new(StrangeName)) + assert.NoError(t, err) + + err = testEngine.CreateTables(new(StrangeName)) + assert.NoError(t, err) + + _, err = testEngine.Insert(&StrangeName{Name: "sfsfdsfds"}) + assert.NoError(t, err) + + beans := make([]StrangeName, 0) + err = testEngine.Find(&beans) + assert.NoError(t, err) +} + +func TestCreatedUpdated(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type CreatedUpdated struct { + Id int64 + Name string + Value float64 `xorm:"numeric"` + Created time.Time `xorm:"created"` + Created2 time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` + } + + err := testEngine.Sync2(&CreatedUpdated{}) + assert.NoError(t, err) + + c := &CreatedUpdated{Name: "test"} + _, err = testEngine.Insert(c) + assert.NoError(t, err) + + c2 := new(CreatedUpdated) + has, err := testEngine.ID(c.Id).Get(c2) + assert.NoError(t, err) + + assert.True(t, has) + + c2.Value -= 1 + _, err = testEngine.ID(c2.Id).Update(c2) + assert.NoError(t, err) +} + +func TestCreatedUpdatedInt64(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type CreatedUpdatedInt64 struct { + Id int64 + Name string + Value float64 `xorm:"numeric"` + Created int64 `xorm:"created"` + Created2 int64 `xorm:"created"` + Updated int64 `xorm:"updated"` + } + + assertSync(t, &CreatedUpdatedInt64{}) + + c := &CreatedUpdatedInt64{Name: "test"} + _, err := testEngine.Insert(c) + assert.NoError(t, err) + + c2 := new(CreatedUpdatedInt64) + has, err := testEngine.ID(c.Id).Get(c2) + assert.NoError(t, err) + assert.True(t, has) + + c2.Value -= 1 + _, err = testEngine.ID(c2.Id).Update(c2) + assert.NoError(t, err) +} + +type Lowercase struct { + Id int64 + Name string + ended int64 `xorm:"-"` +} + +func TestLowerCase(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.Sync2(&Lowercase{}) + assert.NoError(t, err) + _, err = testEngine.Where("id > 0").Delete(&Lowercase{}) + assert.NoError(t, err) + + _, err = testEngine.Insert(&Lowercase{ended: 1}) + assert.NoError(t, err) + + ls := make([]Lowercase, 0) + err = testEngine.Find(&ls) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(ls)) +} + +func TestAutoIncrTag(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TestAutoIncr1 struct { + Id int64 + } + + tb := testEngine.TableInfo(new(TestAutoIncr1)) + cols := tb.Columns() + assert.EqualValues(t, 1, len(cols)) + assert.True(t, cols[0].IsAutoIncrement) + assert.True(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + + type TestAutoIncr2 struct { + Id int64 `xorm:"id"` + } + + tb = testEngine.TableInfo(new(TestAutoIncr2)) + 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) + + type TestAutoIncr3 struct { + Id int64 `xorm:"'ID'"` + } + + tb = testEngine.TableInfo(new(TestAutoIncr3)) + 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) + + type TestAutoIncr4 struct { + Id int64 `xorm:"pk"` + } + + tb = testEngine.TableInfo(new(TestAutoIncr4)) + cols = tb.Columns() + assert.EqualValues(t, 1, len(cols)) + assert.False(t, cols[0].IsAutoIncrement) + assert.True(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) +} + +func TestTagComment(t *testing.T) { + assert.NoError(t, prepareEngine()) + // FIXME: only support mysql + if testEngine.Dialect().DriverName() != schemas.MYSQL { + return + } + + type TestComment1 struct { + Id int64 `xorm:"comment(主键)"` + } + + assert.NoError(t, testEngine.Sync2(new(TestComment1))) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 1, len(tables[0].Columns())) + assert.EqualValues(t, "主键", tables[0].Columns()[0].Comment) + + assert.NoError(t, testEngine.DropTables(new(TestComment1))) + + type TestComment2 struct { + Id int64 `xorm:"comment('主键')"` + } + + assert.NoError(t, testEngine.Sync2(new(TestComment2))) + + tables, err = testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 1, len(tables[0].Columns())) + assert.EqualValues(t, "主键", tables[0].Columns()[0].Comment) +} + +func TestTagDefault(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DefaultStruct struct { + Id int64 + Name string + Age int `xorm:"default(10)"` + } + + assertSync(t, new(DefaultStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + var defaultVal string + var isDefaultExist bool + tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct") + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("age") + assert.NotNil(t, col) + defaultVal = col.Default + isDefaultExist = !col.DefaultIsEmpty + break + } + } + assert.True(t, isDefaultExist) + assert.EqualValues(t, "10", defaultVal) + + cnt, err := testEngine.Omit("age").Insert(&DefaultStruct{ + Name: "test", + Age: 20, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var s DefaultStruct + has, err := testEngine.ID(1).Get(&s) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 10, s.Age) + assert.EqualValues(t, "test", s.Name) +} + +func TestTagDefault2(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DefaultStruct2 struct { + Id int64 + Name string + } + + assertSync(t, new(DefaultStruct2)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + var defaultVal string + var isDefaultExist bool + tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct2") + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("name") + assert.NotNil(t, col) + defaultVal = col.Default + isDefaultExist = !col.DefaultIsEmpty + break + } + } + assert.False(t, isDefaultExist, fmt.Sprintf("default value is --%v--", defaultVal)) + assert.EqualValues(t, "", defaultVal) +} + +func TestTagDefault3(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DefaultStruct3 struct { + Id int64 + Name string `xorm:"default('myname')"` + } + + assertSync(t, new(DefaultStruct3)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + var defaultVal string + var isDefaultExist bool + tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct3") + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("name") + assert.NotNil(t, col) + defaultVal = col.Default + isDefaultExist = !col.DefaultIsEmpty + break + } + } + assert.True(t, isDefaultExist) + assert.EqualValues(t, "'myname'", defaultVal) +} + +func TestTagDefault4(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DefaultStruct4 struct { + Id int64 + Created time.Time `xorm:"default(CURRENT_TIMESTAMP)"` + } + + assertSync(t, new(DefaultStruct4)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + var defaultVal string + var isDefaultExist bool + tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct4") + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("created") + assert.NotNil(t, col) + defaultVal = col.Default + isDefaultExist = !col.DefaultIsEmpty + break + } + } + assert.True(t, isDefaultExist) + assert.True(t, "CURRENT_TIMESTAMP" == defaultVal || + "now()" == defaultVal || + "getdate" == defaultVal, defaultVal) +} + +func TestTagDefault5(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DefaultStruct5 struct { + Id int64 + Created time.Time `xorm:"default('2006-01-02 15:04:05')"` + } + + assertSync(t, new(DefaultStruct5)) + table := testEngine.TableInfo(new(DefaultStruct5)) + createdCol := table.GetColumn("created") + assert.NotNil(t, createdCol) + assert.EqualValues(t, "'2006-01-02 15:04:05'", createdCol.Default) + assert.False(t, createdCol.DefaultIsEmpty) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + var defaultVal string + var isDefaultExist bool + tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct5") + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("created") + assert.NotNil(t, col) + defaultVal = col.Default + isDefaultExist = !col.DefaultIsEmpty + break + } + } + assert.True(t, isDefaultExist) + assert.EqualValues(t, "'2006-01-02 15:04:05'", defaultVal) +} + +func TestTagDefault6(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type DefaultStruct6 struct { + Id int64 + IsMan bool `xorm:"default(true)"` + } + + assertSync(t, new(DefaultStruct6)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + var defaultVal string + var isDefaultExist bool + tableName := testEngine.GetColumnMapper().Obj2Table("DefaultStruct6") + for _, table := range tables { + if table.Name == tableName { + col := table.GetColumn("is_man") + assert.NotNil(t, col) + defaultVal = col.Default + isDefaultExist = !col.DefaultIsEmpty + break + } + } + assert.True(t, isDefaultExist) + if defaultVal == "1" { + defaultVal = "true" + } else if defaultVal == "0" { + defaultVal = "false" + } + assert.EqualValues(t, "true", defaultVal) +} + +func TestTagsDirection(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type OnlyFromDBStruct struct { + Id int64 + Name string + Uuid string `xorm:"<- default '1'"` + } + + assertSync(t, new(OnlyFromDBStruct)) + + cnt, err := testEngine.Insert(&OnlyFromDBStruct{ + Name: "test", + Uuid: "2", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var s OnlyFromDBStruct + has, err := testEngine.ID(1).Get(&s) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "1", s.Uuid) + assert.EqualValues(t, "test", s.Name) + + cnt, err = testEngine.ID(1).Update(&OnlyFromDBStruct{ + Uuid: "3", + Name: "test1", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var s3 OnlyFromDBStruct + has, err = testEngine.ID(1).Get(&s3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "1", s3.Uuid) + assert.EqualValues(t, "test1", s3.Name) + + type OnlyToDBStruct struct { + Id int64 + Name string + Uuid string `xorm:"->"` + } + + assertSync(t, new(OnlyToDBStruct)) + + cnt, err = testEngine.Insert(&OnlyToDBStruct{ + Name: "test", + Uuid: "2", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var s2 OnlyToDBStruct + has, err = testEngine.ID(1).Get(&s2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "", s2.Uuid) + assert.EqualValues(t, "test", s2.Name) +} + +func TestTagTime(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TagUTCStruct struct { + Id int64 + Name string + Created time.Time `xorm:"created utc"` + } + + assertSync(t, new(TagUTCStruct)) + + assert.EqualValues(t, time.Local.String(), testEngine.GetTZLocation().String()) + + s := TagUTCStruct{ + Name: "utc", + } + cnt, err := testEngine.Insert(&s) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var u TagUTCStruct + has, err := testEngine.ID(1).Get(&u) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, s.Created.Format("2006-01-02 15:04:05"), u.Created.Format("2006-01-02 15:04:05")) + + var tm string + has, err = testEngine.Table("tag_u_t_c_struct").Cols("created").Get(&tm) + 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)) +} + +func TestTagAutoIncr(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TagAutoIncr struct { + Id int64 + Name string + } + + assertSync(t, new(TagAutoIncr)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, tableMapper.Obj2Table("TagAutoIncr"), tables[0].Name) + col := tables[0].GetColumn(colMapper.Obj2Table("Id")) + assert.NotNil(t, col) + assert.True(t, col.IsPrimaryKey) + assert.True(t, col.IsAutoIncrement) + + col2 := tables[0].GetColumn(colMapper.Obj2Table("Name")) + assert.NotNil(t, col2) + assert.False(t, col2.IsPrimaryKey) + assert.False(t, col2.IsAutoIncrement) +} + +func TestTagPrimarykey(t *testing.T) { + assert.NoError(t, prepareEngine()) + type TagPrimaryKey struct { + Id int64 `xorm:"pk"` + Name string `xorm:"VARCHAR(20) pk"` + } + + assertSync(t, new(TagPrimaryKey)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, tableMapper.Obj2Table("TagPrimaryKey"), tables[0].Name) + col := tables[0].GetColumn(colMapper.Obj2Table("Id")) + assert.NotNil(t, col) + assert.True(t, col.IsPrimaryKey) + assert.False(t, col.IsAutoIncrement) + + col2 := tables[0].GetColumn(colMapper.Obj2Table("Name")) + assert.NotNil(t, col2) + assert.True(t, col2.IsPrimaryKey) + assert.False(t, col2.IsAutoIncrement) +} + +type VersionS struct { + Id int64 + Name string + Ver int `xorm:"version"` + Created time.Time `xorm:"created"` +} + +func TestVersion1(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(new(VersionS)) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(new(VersionS)) + if err != nil { + t.Error(err) + panic(err) + } + + ver := &VersionS{Name: "sfsfdsfds"} + _, err = testEngine.Insert(ver) + if err != nil { + t.Error(err) + panic(err) + } + fmt.Println(ver) + if ver.Ver != 1 { + err = errors.New("insert error") + t.Error(err) + panic(err) + } + + newVer := new(VersionS) + has, err := testEngine.ID(ver.Id).Get(newVer) + if err != nil { + t.Error(err) + panic(err) + } + + if !has { + t.Error(fmt.Errorf("no version id is %v", ver.Id)) + panic(err) + } + fmt.Println(newVer) + if newVer.Ver != 1 { + err = errors.New("insert error") + t.Error(err) + panic(err) + } + + newVer.Name = "-------" + _, err = testEngine.ID(ver.Id).Update(newVer) + if err != nil { + t.Error(err) + panic(err) + } + if newVer.Ver != 2 { + err = errors.New("update should set version back to struct") + t.Error(err) + } + + newVer = new(VersionS) + has, err = testEngine.ID(ver.Id).Get(newVer) + if err != nil { + t.Error(err) + panic(err) + } + fmt.Println(newVer) + if newVer.Ver != 2 { + err = errors.New("update error") + t.Error(err) + panic(err) + } +} + +func TestVersion2(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(new(VersionS)) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(new(VersionS)) + if err != nil { + t.Error(err) + panic(err) + } + + var vers = []VersionS{ + {Name: "sfsfdsfds"}, + {Name: "xxxxx"}, + } + _, err = testEngine.Insert(vers) + if err != nil { + t.Error(err) + panic(err) + } + + fmt.Println(vers) + + for _, v := range vers { + if v.Ver != 1 { + err := errors.New("version should be 1") + t.Error(err) + panic(err) + } + } +} + +type VersionUintS struct { + Id int64 + Name string + Ver uint `xorm:"version"` + Created time.Time `xorm:"created"` +} + +func TestVersion3(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(new(VersionUintS)) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(new(VersionUintS)) + if err != nil { + t.Error(err) + panic(err) + } + + ver := &VersionUintS{Name: "sfsfdsfds"} + _, err = testEngine.Insert(ver) + if err != nil { + t.Error(err) + panic(err) + } + fmt.Println(ver) + if ver.Ver != 1 { + err = errors.New("insert error") + t.Error(err) + panic(err) + } + + newVer := new(VersionUintS) + has, err := testEngine.ID(ver.Id).Get(newVer) + if err != nil { + t.Error(err) + panic(err) + } + + if !has { + t.Error(fmt.Errorf("no version id is %v", ver.Id)) + panic(err) + } + fmt.Println(newVer) + if newVer.Ver != 1 { + err = errors.New("insert error") + t.Error(err) + panic(err) + } + + newVer.Name = "-------" + _, err = testEngine.ID(ver.Id).Update(newVer) + if err != nil { + t.Error(err) + panic(err) + } + if newVer.Ver != 2 { + err = errors.New("update should set version back to struct") + t.Error(err) + } + + newVer = new(VersionUintS) + has, err = testEngine.ID(ver.Id).Get(newVer) + if err != nil { + t.Error(err) + panic(err) + } + fmt.Println(newVer) + if newVer.Ver != 2 { + err = errors.New("update error") + t.Error(err) + panic(err) + } +} + +func TestVersion4(t *testing.T) { + assert.NoError(t, prepareEngine()) + + err := testEngine.DropTables(new(VersionUintS)) + if err != nil { + t.Error(err) + panic(err) + } + + err = testEngine.CreateTables(new(VersionUintS)) + if err != nil { + t.Error(err) + panic(err) + } + + var vers = []VersionUintS{ + {Name: "sfsfdsfds"}, + {Name: "xxxxx"}, + } + _, err = testEngine.Insert(vers) + if err != nil { + t.Error(err) + panic(err) + } + + fmt.Println(vers) + + for _, v := range vers { + if v.Ver != 1 { + err := errors.New("version should be 1") + t.Error(err) + panic(err) + } + } +} diff --git a/types_test.go b/types_test.go index 1e21907c..53872372 100644 --- a/types_test.go +++ b/types_test.go @@ -9,8 +9,10 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "xorm.io/xorm/convert" "xorm.io/xorm/schemas" + + "github.com/stretchr/testify/assert" ) func TestArrayField(t *testing.T) { @@ -137,8 +139,8 @@ type ConvStruct struct { Conv ConvString Conv2 *ConvString Cfg1 ConvConfig - Cfg2 *ConvConfig `xorm:"TEXT"` - Cfg3 Conversion `xorm:"BLOB"` + Cfg2 *ConvConfig `xorm:"TEXT"` + Cfg3 convert.Conversion `xorm:"BLOB"` Slice SliceType } @@ -267,11 +269,11 @@ type Status struct { } var ( - _ Conversion = &Status{} - Registered Status = Status{"Registered", "white"} - Approved Status = Status{"Approved", "green"} - Removed Status = Status{"Removed", "red"} - Statuses map[string]Status = map[string]Status{ + _ convert.Conversion = &Status{} + Registered Status = Status{"Registered", "white"} + Approved Status = Status{"Approved", "green"} + Removed Status = Status{"Removed", "red"} + Statuses map[string]Status = map[string]Status{ Registered.Name: Registered, Approved.Name: Approved, Removed.Name: Removed, diff --git a/xorm.go b/xorm.go index 0f27f8e4..f3230aa1 100644 --- a/xorm.go +++ b/xorm.go @@ -21,6 +21,7 @@ import ( "xorm.io/xorm/log" "xorm.io/xorm/names" "xorm.io/xorm/schemas" + "xorm.io/xorm/tags" ) const ( @@ -65,9 +66,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { dialect: dialect, Tables: make(map[reflect.Type]*schemas.Table), mutex: &sync.RWMutex{}, - TagIdentifier: "xorm", TZLocation: time.Local, - tagHandlers: defaultTagHandlers, defaultContext: context.Background(), cacherMgr: caches.NewManager(), } @@ -81,7 +80,8 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { logger := log.NewSimpleLogger(os.Stdout) logger.SetLevel(log.LOG_INFO) engine.SetLogger(logger) - engine.SetMapper(names.NewCacheMapper(new(names.SnakeMapper))) + mapper := names.NewCacheMapper(new(names.SnakeMapper)) + engine.tagParser = tags.NewParser("xorm", dialect, mapper, mapper, engine.cacherMgr) runtime.SetFinalizer(engine, close) From 7bf9a7a73c5e3f93b2abdbf1486b638041b07c91 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 05:49:43 +0000 Subject: [PATCH 038/112] Improve statement (#1555) Fix test Improve statement Reviewed-on: https://gitea.com/xorm/xorm/pulls/1555 --- schemas/quote.go | 17 +++++++---- schemas/quote_test.go | 12 ++++++-- statement.go | 66 +++++++++++++++++++++---------------------- statement_args.go | 4 +-- 4 files changed, 54 insertions(+), 45 deletions(-) diff --git a/schemas/quote.go b/schemas/quote.go index d10a5dc8..0e022240 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -66,13 +66,18 @@ func (q Quoter) Trim(s string) string { return s } - if s[0:1] == q[0] { - s = s[1:] + var buf strings.Builder + for i := 0; i < len(s); i++ { + switch { + case i == 0 && s[i:i+1] == q[0]: + case i == len(s)-1 && s[i:i+1] == q[1]: + case s[i:i+1] == q[1] && s[i+1] == '.': + case s[i:i+1] == q[0] && s[i-1] == '.': + default: + buf.WriteByte(s[i]) + } } - if len(s) > 0 && s[len(s)-1:] == q[1] { - return s[:len(s)-1] - } - return s + return buf.String() } func (q Quoter) Join(a []string, sep string) string { diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 174d1a0d..24739377 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -65,7 +65,13 @@ func TestStrings(t *testing.T) { } func TestTrim(t *testing.T) { - raw := "[table_name]" - assert.EqualValues(t, raw, CommonQuoter.Trim(raw)) - assert.EqualValues(t, "table_name", Quoter{"[", "]"}.Trim(raw)) + var kases = map[string]string{ + "[table_name]": "table_name", + "[schema].[table_name]": "schema.table_name", + } + + for src, dst := range kases { + assert.EqualValues(t, src, CommonQuoter.Trim(src)) + assert.EqualValues(t, dst, Quoter{"[", "]"}.Trim(src)) + } } diff --git a/statement.go b/statement.go index d3048601..c07ddfe9 100644 --- a/statement.go +++ b/statement.go @@ -615,7 +615,7 @@ func (statement *Statement) Cols(columns ...string) *Statement { } func (statement *Statement) columnStr() string { - return statement.Engine.dialect.Quoter().Join(statement.columnMap, ", ") + return statement.dialect.Quoter().Join(statement.columnMap, ", ") } // AllCols update use only: update all columns @@ -750,10 +750,11 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition statement.lastError = err return statement } - tbs := strings.Split(tp.TableName(), ".") - quotes := append(strings.Split(statement.Engine.Quote(""), ""), "`") - var aliasName = strings.Trim(tbs[len(tbs)-1], strings.Join(quotes, "")) + 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", subSQL, aliasName, condition) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) case *builder.Builder: @@ -762,17 +763,18 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition statement.lastError = err return statement } - tbs := strings.Split(tp.TableName(), ".") - quotes := append(strings.Split(statement.Engine.Quote(""), ""), "`") - var aliasName = strings.Trim(tbs[len(tbs)-1], strings.Join(quotes, "")) + 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", subSQL, aliasName, condition) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) default: tbName := statement.Engine.TableName(tablename, true) if !isSubQuery(tbName) { var buf strings.Builder - statement.Engine.QuoteTo(&buf, tbName) + statement.dialect.Quoter().QuoteTo(&buf, tbName) tbName = buf.String() } fmt.Fprintf(&buf, "%s ON %v", tbName, condition) @@ -836,14 +838,14 @@ func (statement *Statement) genColumnStr() string { buf.WriteString(".") } - statement.Engine.QuoteTo(&buf, col.Name) + statement.dialect.Quoter().QuoteTo(&buf, col.Name) } return buf.String() } func (statement *Statement) genCreateTableSQL() string { - return statement.Engine.dialect.CreateTableSQL(statement.RefTable, statement.TableName(), + return statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName(), statement.StoreEngine, statement.Charset) } @@ -852,11 +854,7 @@ func (statement *Statement) genIndexSQL() []string { tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { if index.Type == schemas.IndexType { - sql := statement.Engine.dialect.CreateIndexSQL(tbName, index) - /*idxTBName := strings.Replace(tbName, ".", "_", -1) - idxTBName = strings.Replace(idxTBName, `"`, "", -1) - sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(idxTBName, idxName)), - quote(tbName), quote(strings.Join(index.Cols, quote(","))))*/ + sql := statement.dialect.CreateIndexSQL(tbName, index) sqls = append(sqls, sql) } } @@ -872,7 +870,7 @@ func (statement *Statement) genUniqueSQL() []string { tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { if index.Type == schemas.UniqueType { - sql := statement.Engine.dialect.CreateIndexSQL(tbName, index) + sql := statement.dialect.CreateIndexSQL(tbName, index) sqls = append(sqls, sql) } } @@ -895,9 +893,9 @@ func (statement *Statement) genDelIndexSQL() []string { } else if index.Type == schemas.IndexType { rIdxName = indexName(idxPrefixName, idxName) } - sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(statement.Engine.TableName(rIdxName, true))) - if statement.Engine.dialect.IndexOnTable() { - sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(tbName)) + sql := fmt.Sprintf("DROP INDEX %v", statement.quote(statement.Engine.TableName(rIdxName, true))) + if statement.dialect.IndexOnTable() { + sql += fmt.Sprintf(" ON %v", statement.quote(tbName)) } sqls = append(sqls, sql) } @@ -905,10 +903,10 @@ func (statement *Statement) genDelIndexSQL() []string { } func (statement *Statement) genAddColumnStr(col *schemas.Column) (string, []interface{}) { - quote := statement.Engine.Quote + quote := statement.quote sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quote(statement.TableName()), - dialects.String(statement.Engine.dialect, col)) - if statement.Engine.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { + dialects.String(statement.dialect, col)) + if statement.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { sql += " COMMENT '" + col.Comment + "'" } sql += ";" @@ -946,7 +944,7 @@ func (statement *Statement) genConds(bean interface{}) (string, []interface{}, e func (statement *Statement) quoteColumnStr(columnStr string) string { columns := strings.Split(columnStr, ",") - return statement.Engine.dialect.Quoter().Join(columns, ",") + return statement.dialect.Quoter().Join(columns, ",") } func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, error) { @@ -1040,7 +1038,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri var sumStrs = make([]string, 0, len(columns)) for _, colName := range columns { if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { - colName = statement.Engine.Quote(colName) + colName = statement.quote(colName) } sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName)) } @@ -1062,8 +1060,8 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) { var ( distinct string - dialect = statement.Engine.Dialect() - quote = statement.Engine.Quote + dialect = statement.dialect + quote = statement.quote fromStr = " FROM " top, mssqlCondi, whereStr string ) @@ -1207,10 +1205,10 @@ func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName var colnames = make([]string, len(cols)) for i, col := range cols { if includeTableName { - colnames[i] = statement.Engine.Quote(statement.TableName()) + - "." + statement.Engine.Quote(col.Name) + colnames[i] = statement.quote(statement.TableName()) + + "." + statement.quote(col.Name) } else { - colnames[i] = statement.Engine.Quote(col.Name) + colnames[i] = statement.quote(col.Name) } } return strings.Join(colnames, ", ") @@ -1231,7 +1229,7 @@ func (statement *Statement) convertIDSQL(sqlStr string) string { var top string pLimitN := statement.LimitN - if pLimitN != nil && statement.Engine.dialect.DBType() == schemas.MSSQL { + if pLimitN != nil && statement.dialect.DBType() == schemas.MSSQL { top = fmt.Sprintf("TOP %d ", *pLimitN) } @@ -1251,7 +1249,7 @@ func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { if len(sqls) != 2 { if len(sqls) == 1 { return sqls[0], fmt.Sprintf("SELECT %v FROM %v", - colstrs, statement.Engine.Quote(statement.TableName())) + colstrs, statement.quote(statement.TableName())) } return "", "" } @@ -1260,9 +1258,9 @@ func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { // TODO: for postgres only, if any other database? var paraStr string - if statement.Engine.dialect.DBType() == schemas.POSTGRES { + if statement.dialect.DBType() == schemas.POSTGRES { paraStr = "$" - } else if statement.Engine.dialect.DBType() == schemas.MSSQL { + } else if statement.dialect.DBType() == schemas.MSSQL { paraStr = ":" } @@ -1278,6 +1276,6 @@ func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { } return sqls[0], fmt.Sprintf("SELECT %v FROM %v WHERE %v", - colstrs, statement.Engine.Quote(statement.TableName()), + colstrs, statement.quote(statement.TableName()), whereStr) } diff --git a/statement_args.go b/statement_args.go index 4f35ce6e..22bfeb7b 100644 --- a/statement_args.go +++ b/statement_args.go @@ -80,7 +80,7 @@ const insertSelectPlaceHolder = true func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case bool: - if statement.Engine.dialect.DBType() == schemas.MSSQL { + if statement.dialect.DBType() == schemas.MSSQL { if argv { if _, err := w.WriteString("1"); err != nil { return err @@ -119,7 +119,7 @@ func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) er w.Append(arg) } else { var convertFunc = convertStringSingleQuote - if statement.Engine.dialect.DBType() == schemas.MYSQL { + if statement.dialect.DBType() == schemas.MYSQL { convertFunc = convertString } if _, err := w.WriteString(convertArg(arg, convertFunc)); err != nil { From 4c2b0e0f551384083b419cc0d7f797462a12fdff Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Feb 2020 15:31:05 +0000 Subject: [PATCH 039/112] Add context for dialects (#1558) More improvements Add context for dialects Reviewed-on: https://gitea.com/xorm/xorm/pulls/1558 --- dialects/dialect.go | 157 +++++++++++++++++++------------------------ dialects/mssql.go | 21 +++--- dialects/mysql.go | 17 ++--- dialects/oracle.go | 46 +++---------- dialects/postgres.go | 17 ++--- dialects/sqlite3.go | 17 ++--- engine.go | 10 +-- session_schema.go | 8 +-- statement.go | 11 --- 9 files changed, 128 insertions(+), 176 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 3ed867f4..26d6521a 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "fmt" "strings" "time" @@ -60,23 +61,21 @@ type Dialect interface { IndexCheckSQL(tableName, idxName string) (string, []interface{}) TableCheckSQL(tableName string) (string, []interface{}) - IsColumnExist(tableName string, colName string) (bool, error) + IsColumnExist(ctx context.Context, tableName string, colName string) (bool, error) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string DropTableSQL(tableName string) string CreateIndexSQL(tableName string, index *schemas.Index) string DropIndexSQL(tableName string, index *schemas.Index) string + AddColumnSQL(tableName string, col *schemas.Column) string ModifyColumnSQL(tableName string, col *schemas.Column) string ForUpdateSQL(query string) string - // CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error - // MustDropTable(tableName string) error - - GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) - GetTables() ([]*schemas.Table, error) - GetIndexes(tableName string) (map[string]*schemas.Index, error) + GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) + GetTables(ctx context.Context) ([]*schemas.Table, error) + GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) Filters() []Filter SetParams(params map[string]string) @@ -96,55 +95,6 @@ type Base struct { uri *URI } -// String generate column description string according dialect -func String(d Dialect, col *schemas.Column) string { - sql := d.Quoter().Quote(col.Name) + " " - - sql += d.SQLType(col) + " " - - if col.IsPrimaryKey { - sql += "PRIMARY KEY " - if col.IsAutoIncrement { - sql += d.AutoIncrStr() + " " - } - } - - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } - - if d.ShowCreateNull() { - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } - } - - return sql -} - -// StringNoPk generate column description string according dialect without primary keys -func StringNoPk(d Dialect, col *schemas.Column) string { - sql := d.Quoter().Quote(col.Name) + " " - - sql += d.SQLType(col) + " " - - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } - - if d.ShowCreateNull() { - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } - } - - return sql -} - func (b *Base) DB() *core.DB { return b.db } @@ -167,6 +117,55 @@ func (b *Base) DBType() DBType { return b.uri.DBType } +// String generate column description string according dialect +func (b *Base) String(col *schemas.Column) string { + sql := b.dialect.Quoter().Quote(col.Name) + " " + + sql += b.dialect.SQLType(col) + " " + + if col.IsPrimaryKey { + sql += "PRIMARY KEY " + if col.IsAutoIncrement { + sql += b.dialect.AutoIncrStr() + " " + } + } + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + if b.dialect.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + return sql +} + +// StringNoPk generate column description string according dialect without primary keys +func (b *Base) StringNoPk(col *schemas.Column) string { + sql := b.dialect.Quoter().Quote(col.Name) + " " + + sql += b.dialect.SQLType(col) + " " + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + if b.dialect.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + return sql +} + func (b *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } @@ -196,9 +195,9 @@ func (db *Base) DropTableSQL(tableName string) string { return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)) } -func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { +func (db *Base) HasRecords(ctx context.Context, query string, args ...interface{}) (bool, error) { db.LogSQL(query, args) - rows, err := db.DB().Query(query, args...) + rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { return false, err } @@ -210,7 +209,7 @@ func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { return false, nil } -func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { +func (db *Base) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { quote := db.dialect.Quoter().Quote query := fmt.Sprintf( "SELECT %v FROM %v.%v WHERE %v = ? AND %v = ? AND %v = ?", @@ -221,32 +220,18 @@ func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { quote("TABLE_NAME"), quote("COLUMN_NAME"), ) - return db.HasRecords(query, db.uri.DBName, tableName, colName) + return db.HasRecords(ctx, query, db.uri.DBName, tableName, colName) } -/* -func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error { - sql, args := db.dialect.TableCheckSQL(tableName) - rows, err := db.DB().Query(sql, args...) - if db.Logger != nil { - db.Logger.Info("[sql]", sql, args) +func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { + quoter := db.dialect.Quoter() + sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), + db.String(col)) + if db.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { + sql += " COMMENT '" + col.Comment + "'" } - if err != nil { - return err - } - defer rows.Close() - - if rows.Next() { - return nil - } - - sql = db.dialect.CreateTableSQL(table, tableName, storeEngine, charset) - _, err = db.DB().Exec(sql) - if db.Logger != nil { - db.Logger.Info("[sql]", sql) - } - return err -}*/ + return sql +} func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { quoter := db.dialect.Quoter() @@ -273,7 +258,7 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { } func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { - return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, StringNoPk(db.dialect, col)) + return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, db.StringNoPk(col)) } func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { @@ -293,12 +278,12 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += String(b.dialect, col) + sql += b.String(col) } else { - sql += StringNoPk(b.dialect, col) + sql += b.StringNoPk(col) } sql = strings.TrimSpace(sql) - if b.DriverName() == schemas.MYSQL && len(col.Comment) > 0 { + if b.DBType() == schemas.MYSQL && len(col.Comment) > 0 { sql += " COMMENT '" + col.Comment + "'" } sql += ", " diff --git a/dialects/mssql.go b/dialects/mssql.go index 74a3bb63..83844f4e 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "errors" "fmt" "net/url" @@ -324,10 +325,10 @@ func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{} return sql, args }*/ -func (db *mssql) IsColumnExist(tableName, colName string) (bool, error) { +func (db *mssql) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { query := `SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "COLUMN_NAME" = ?` - return db.HasRecords(query, tableName, colName) + return db.HasRecords(ctx, query, tableName, colName) } func (db *mssql) TableCheckSQL(tableName string) (string, []interface{}) { @@ -336,7 +337,7 @@ func (db *mssql) TableCheckSQL(tableName string) (string, []interface{}) { return sql, args } -func (db *mssql) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{} s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, "default_is_null" = (CASE WHEN c.text is null THEN 1 ELSE 0 END), @@ -352,7 +353,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*schemas.Col where a.object_id=object_id('` + tableName + `')` db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -407,12 +408,12 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*schemas.Col return colSeq, cols, nil } -func (db *mssql) GetTables() ([]*schemas.Table, error) { +func (db *mssql) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := `select name from sysobjects where xtype ='U'` db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -432,7 +433,7 @@ func (db *mssql) GetTables() ([]*schemas.Table, error) { return tables, nil } -func (db *mssql) GetIndexes(tableName string) (map[string]*schemas.Index, error) { +func (db *mssql) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := `SELECT IXS.NAME AS [INDEX_NAME], @@ -447,7 +448,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? ` db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -510,9 +511,9 @@ func (db *mssql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += String(db, col) + sql += db.String(col) } else { - sql += StringNoPk(db, col) + sql += db.StringNoPk(col) } sql = strings.TrimSpace(sql) sql += ", " diff --git a/dialects/mysql.go b/dialects/mysql.go index 32dc25b7..62fc6eb1 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "crypto/tls" "errors" "fmt" @@ -314,13 +315,13 @@ func (db *mysql) TableCheckSQL(tableName string) (string, []interface{}) { return sql, args } -func (db *mysql) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{db.uri.DBName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -425,13 +426,13 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*schemas.Col return colSeq, cols, nil } -func (db *mysql) GetTables() ([]*schemas.Table, error) { +func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{db.uri.DBName} s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -455,12 +456,12 @@ func (db *mysql) GetTables() ([]*schemas.Table, error) { return tables, nil } -func (db *mysql) GetIndexes(tableName string) (map[string]*schemas.Index, error) { +func (db *mysql) GetIndexes(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` = ?" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -523,9 +524,9 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += String(db, col) + sql += db.String(col) } else { - sql += StringNoPk(db, col) + sql += db.StringNoPk(col) } sql = strings.TrimSpace(sql) if len(col.Comment) > 0 { diff --git a/dialects/oracle.go b/dialects/oracle.go index 46f7aca2..1247d7a4 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "errors" "fmt" "regexp" @@ -592,7 +593,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c /*if col.IsPrimaryKey && len(pkList) == 1 { sql += col.String(b.dialect) } else {*/ - sql += StringNoPk(db, col) + sql += db.StringNoPk(col) // } sql = strings.TrimSpace(sql) sql += ", " @@ -630,40 +631,13 @@ func (db *oracle) TableCheckSQL(tableName string) (string, []interface{}) { return `SELECT table_name FROM user_tables WHERE table_name = :1`, args } -func (db *oracle) MustDropTable(tableName string) error { - sql, args := db.TableCheckSQL(tableName) - db.LogSQL(sql, args) - - rows, err := db.DB().Query(sql, args...) - if err != nil { - return err - } - defer rows.Close() - - if !rows.Next() { - return nil - } - - sql = "Drop Table \"" + tableName + "\"" - db.LogSQL(sql, args) - - _, err = db.DB().Exec(sql) - return err -} - -/*func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(colName)} - return "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" + - " AND column_name = ?", args -}*/ - -func (db *oracle) IsColumnExist(tableName, colName string) (bool, error) { +func (db *oracle) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { args := []interface{}{tableName, colName} query := "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = :1" + " AND column_name = :2" db.LogSQL(query, args) - rows, err := db.DB().Query(query, args...) + rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { return false, err } @@ -675,13 +649,13 @@ func (db *oracle) IsColumnExist(tableName, colName string) (bool, error) { return false, nil } -func (db *oracle) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *oracle) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -773,12 +747,12 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*schemas.Co return colSeq, cols, nil } -func (db *oracle) GetTables() ([]*schemas.Table, error) { +func (db *oracle) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT table_name FROM user_tables" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -797,13 +771,13 @@ func (db *oracle) GetTables() ([]*schemas.Table, error) { return tables, nil } -func (db *oracle) GetIndexes(tableName string) (map[string]*schemas.Index, error) { +func (db *oracle) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{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 =:1" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/dialects/postgres.go b/dialects/postgres.go index cab7eaef..d6847b02 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "errors" "fmt" "net/url" @@ -929,7 +930,7 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName)) } -func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { +func (db *postgres) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { args := []interface{}{db.uri.Schema, tableName, colName} query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" + " AND column_name = $3" @@ -940,7 +941,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { } db.LogSQL(query, args) - rows, err := db.DB().Query(query, args...) + rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { return false, err } @@ -949,7 +950,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { return rows.Next(), nil } -func (db *postgres) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *postgres) GetColumns(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, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, @@ -972,7 +973,7 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -1064,7 +1065,7 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att return colSeq, cols, nil } -func (db *postgres) GetTables() ([]*schemas.Table, error) { +func (db *postgres) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT tablename FROM pg_tables" if len(db.uri.Schema) != 0 { @@ -1074,7 +1075,7 @@ func (db *postgres) GetTables() ([]*schemas.Table, error) { db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -1105,7 +1106,7 @@ func getIndexColName(indexdef string) []string { return colNames } -func (db *postgres) GetIndexes(tableName string) (map[string]*schemas.Index, error) { +func (db *postgres) GetIndexes(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") if len(db.uri.Schema) != 0 { @@ -1114,7 +1115,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*schemas.Index, err } db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 0fd80b73..5511468f 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "database/sql" "errors" "fmt" @@ -254,11 +255,11 @@ func (db *sqlite3) ForUpdateSQL(query string) string { return sql, args }*/ -func (db *sqlite3) IsColumnExist(tableName, colName string) (bool, error) { +func (db *sqlite3) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { args := []interface{}{tableName} query := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" db.LogSQL(query, args) - rows, err := db.DB().Query(query, args...) + rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { return false, err } @@ -332,11 +333,11 @@ func parseString(colStr string) (*schemas.Column, error) { return col, nil } -func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *sqlite3) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -389,12 +390,12 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*schemas.C return colSeq, cols, nil } -func (db *sqlite3) GetTables() ([]*schemas.Table, error) { +func (db *sqlite3) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT name FROM sqlite_master WHERE type='table'" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -415,12 +416,12 @@ func (db *sqlite3) GetTables() ([]*schemas.Table, error) { return tables, nil } -func (db *sqlite3) GetIndexes(tableName string) (map[string]*schemas.Index, error) { +func (db *sqlite3) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/engine.go b/engine.go index b97d1c06..50b0958c 100644 --- a/engine.go +++ b/engine.go @@ -321,14 +321,14 @@ func (engine *Engine) NoAutoCondition(no ...bool) *Session { } func (engine *Engine) loadTableInfo(table *schemas.Table) error { - colSeq, cols, err := engine.dialect.GetColumns(table.Name) + colSeq, cols, err := engine.dialect.GetColumns(engine.defaultContext, table.Name) if err != nil { return err } for _, name := range colSeq { table.AddColumn(cols[name]) } - indexes, err := engine.dialect.GetIndexes(table.Name) + indexes, err := engine.dialect.GetIndexes(engine.defaultContext, table.Name) if err != nil { return err } @@ -348,7 +348,7 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { // DBMetas Retrieve all tables, columns, indexes' informations from database. func (engine *Engine) DBMetas() ([]*schemas.Table, error) { - tables, err := engine.dialect.GetTables() + tables, err := engine.dialect.GetTables(engine.defaultContext) if err != nil { return nil, err } @@ -439,7 +439,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...dia colNames := engine.dialect.Quoter().Join(cols, ", ") destColNames := dialect.Quoter().Join(cols, ", ") - rows, err := engine.DB().Query("SELECT " + colNames + " FROM " + engine.Quote(table.Name)) + rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(table.Name)) if err != nil { return err } @@ -979,7 +979,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } else { for _, col := range table.Columns() { - isExist, err := engine.dialect.IsColumnExist(tableNameNoSchema, col.Name) + isExist, err := engine.dialect.IsColumnExist(session.ctx, tableNameNoSchema, col.Name) if err != nil { return err } diff --git a/session_schema.go b/session_schema.go index 809f158f..05b24c91 100644 --- a/session_schema.go +++ b/session_schema.go @@ -183,7 +183,7 @@ func (session *Session) isTableEmpty(tableName string) (bool, error) { // 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(tableName) + indexes, err := session.engine.dialect.GetIndexes(session.ctx, tableName) if err != nil { return false, err } @@ -201,8 +201,8 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo func (session *Session) addColumn(colName string) error { col := session.statement.RefTable.GetColumn(colName) - sql, args := session.statement.genAddColumnStr(col) - _, err := session.exec(sql, args...) + sql := session.statement.dialect.AddColumnSQL(session.statement.TableName(), col) + _, err := session.exec(sql) return err } @@ -229,7 +229,7 @@ func (session *Session) Sync2(beans ...interface{}) error { defer session.Close() } - tables, err := engine.dialect.GetTables() + tables, err := engine.dialect.GetTables(session.ctx) if err != nil { return err } diff --git a/statement.go b/statement.go index c07ddfe9..b1593621 100644 --- a/statement.go +++ b/statement.go @@ -902,17 +902,6 @@ func (statement *Statement) genDelIndexSQL() []string { return sqls } -func (statement *Statement) genAddColumnStr(col *schemas.Column) (string, []interface{}) { - quote := statement.quote - sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quote(statement.TableName()), - dialects.String(statement.dialect, col)) - if statement.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { - sql += " COMMENT '" + col.Comment + "'" - } - sql += ";" - return sql, []interface{}{} -} - func (statement *Statement) buildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { return statement.Engine.buildConds(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) From 29c93926a75cdc813810ab8029b09b516d73f1f9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Feb 2020 00:01:46 +0000 Subject: [PATCH 040/112] Add minial go version go1.11 check on drone (#1560) Add minial go version go1.11 check on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1560 --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index ef48cd60..eb8ed320 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,7 +3,7 @@ kind: pipeline name: testing steps: - name: test-vet - image: golang:1.12 + image: golang:1.11 environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" From f63b42ff9bf2dc197cec4e187439d149e99aed33 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Feb 2020 02:00:21 +0000 Subject: [PATCH 041/112] Move maptype to tag parser (#1561) Move maptype to tag parser Reviewed-on: https://gitea.com/xorm/xorm/pulls/1561 --- engine.go | 54 +++++++----------------------------------- engine_cond.go | 8 +++---- session.go | 2 +- session_convert.go | 6 ++--- session_find.go | 2 +- statement.go | 14 +++++------ tags/parser.go | 33 ++++++++++++++++++++++++++ tags/tag.go | 2 +- tags_test.go | 59 ++++++++++------------------------------------ xorm.go | 13 +++++----- 10 files changed, 77 insertions(+), 116 deletions(-) diff --git a/engine.go b/engine.go index 50b0958c..1bf42d15 100644 --- a/engine.go +++ b/engine.go @@ -9,7 +9,6 @@ import ( "bytes" "context" "database/sql" - "encoding/gob" "errors" "fmt" "io" @@ -17,7 +16,6 @@ import ( "reflect" "strconv" "strings" - "sync" "time" "xorm.io/builder" @@ -37,10 +35,6 @@ type Engine struct { db *core.DB dialect dialects.Dialect - Tables map[reflect.Type]*schemas.Table - - mutex *sync.RWMutex - showSQL bool showExecTime bool @@ -753,43 +747,6 @@ func (engine *Engine) Having(conditions string) *Session { return session.Having(conditions) } -// UnMapType removes the database mapper of a type -func (engine *Engine) UnMapType(t reflect.Type) { - engine.mutex.Lock() - defer engine.mutex.Unlock() - delete(engine.Tables, t) -} - -func (engine *Engine) autoMapType(v reflect.Value) (*schemas.Table, error) { - t := v.Type() - engine.mutex.Lock() - defer engine.mutex.Unlock() - table, ok := engine.Tables[t] - if !ok { - var err error - table, err = engine.tagParser.MapType(v) - if err != nil { - return nil, err - } - - engine.Tables[t] = table - if engine.GetDefaultCacher() != nil { - if v.CanAddr() { - engine.GobRegister(v.Addr().Interface()) - } else { - engine.GobRegister(v.Interface()) - } - } - } - return table, nil -} - -// GobRegister register one struct to gob for cache use -func (engine *Engine) GobRegister(v interface{}) *Engine { - gob.Register(v) - return engine -} - // Table table struct type Table struct { *schemas.Table @@ -804,7 +761,7 @@ func (t *Table) IsValid() bool { // TableInfo get table info according to bean's content func (engine *Engine) TableInfo(bean interface{}) *Table { v := rValue(bean) - tb, err := engine.autoMapType(v) + tb, err := engine.tagParser.MapType(v) if err != nil { engine.logger.Error(err) } @@ -842,7 +799,7 @@ func (engine *Engine) IDOfV(rv reflect.Value) schemas.PK { func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) { v := reflect.Indirect(rv) - table, err := engine.autoMapType(v) + table, err := engine.tagParser.MapType(v) if err != nil { return nil, err } @@ -938,6 +895,11 @@ func (engine *Engine) ClearCache(beans ...interface{}) error { return nil } +// UnMapType remove table from tables cache +func (engine *Engine) UnMapType(t reflect.Type) { + engine.tagParser.ClearTable(t) +} + // Sync the new struct changes to database, this method will automatically add // table, column, index, unique. but will not delete or change anything. // If you change some field, you should change the database manually. @@ -948,7 +910,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { for _, bean := range beans { v := rValue(bean) tableNameNoSchema := engine.TableName(bean) - table, err := engine.autoMapType(v) + table, err := engine.tagParser.MapType(v) if err != nil { return err } diff --git a/engine_cond.go b/engine_cond.go index 00bfd59d..e757df11 100644 --- a/engine_cond.go +++ b/engine_cond.go @@ -165,8 +165,10 @@ func (engine *Engine) buildConds(table *schemas.Table, bean interface{}, val = bytes } } else { - engine.autoMapType(fieldValue) - if table, ok := engine.Tables[fieldValue.Type()]; ok { + table, err := engine.tagParser.MapType(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 @@ -180,8 +182,6 @@ func (engine *Engine) buildConds(table *schemas.Table, bean interface{}, //TODO: how to handler? return nil, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys) } - } else { - val = fieldValue.Interface() } } } diff --git a/session.go b/session.go index d4d9f78a..0b0f56c0 100644 --- a/session.go +++ b/session.go @@ -690,7 +690,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } } } else if session.statement.UseCascade { - table, err := session.engine.autoMapType(*fieldValue) + table, err := session.engine.tagParser.MapType(*fieldValue) if err != nil { return nil, err } diff --git a/session_convert.go b/session_convert.go index 04436ec6..e7eabecc 100644 --- a/session_convert.go +++ b/session_convert.go @@ -209,7 +209,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val v = x fieldValue.Set(reflect.ValueOf(v).Convert(fieldType)) } else if session.statement.UseCascade { - table, err := session.engine.autoMapType(*fieldValue) + table, err := session.engine.tagParser.MapType(*fieldValue) if err != nil { return err } @@ -492,7 +492,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val default: if session.statement.UseCascade { structInter := reflect.New(fieldType.Elem()) - table, err := session.engine.autoMapType(structInter.Elem()) + table, err := session.engine.tagParser.MapType(structInter.Elem()) if err != nil { return err } @@ -603,7 +603,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. return v.Value() } - fieldTable, err := session.engine.autoMapType(fieldValue) + fieldTable, err := session.engine.tagParser.MapType(fieldValue) if err != nil { return nil, err } diff --git a/session_find.go b/session_find.go index 492f19e6..6903c1b9 100644 --- a/session_find.go +++ b/session_find.go @@ -275,7 +275,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect if elemType.Kind() == reflect.Struct { var newValue = newElemFunc(fields) dataStruct := rValue(newValue.Interface()) - tb, err := session.engine.autoMapType(dataStruct) + tb, err := session.engine.tagParser.MapType(dataStruct) if err != nil { return err } diff --git a/statement.go b/statement.go index b1593621..3a823d82 100644 --- a/statement.go +++ b/statement.go @@ -225,7 +225,7 @@ func (statement *Statement) NotIn(column string, args ...interface{}) *Statement func (statement *Statement) setRefValue(v reflect.Value) error { var err error - statement.RefTable, err = statement.Engine.autoMapType(reflect.Indirect(v)) + statement.RefTable, err = statement.Engine.tagParser.MapType(reflect.Indirect(v)) if err != nil { return err } @@ -235,7 +235,7 @@ func (statement *Statement) setRefValue(v reflect.Value) error { func (statement *Statement) setRefBean(bean interface{}) error { var err error - statement.RefTable, err = statement.Engine.autoMapType(rValue(bean)) + statement.RefTable, err = statement.Engine.tagParser.MapType(rValue(bean)) if err != nil { return err } @@ -414,8 +414,10 @@ func (statement *Statement) buildUpdates(bean interface{}, val, _ = nulType.Value() } else { if !col.SQLType.IsJson() { - engine.autoMapType(fieldValue) - if table, ok := engine.Tables[fieldValue.Type()]; ok { + table, err := engine.tagParser.MapType(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 @@ -428,8 +430,6 @@ func (statement *Statement) buildUpdates(bean interface{}, // TODO: how to handler? panic("not supported") } - } else { - val = fieldValue.Interface() } } else { // Blank struct could not be as update data @@ -723,7 +723,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { t := v.Type() if t.Kind() == reflect.Struct { var err error - statement.RefTable, err = statement.Engine.autoMapType(v) + statement.RefTable, err = statement.Engine.tagParser.MapType(v) if err != nil { statement.Engine.logger.Error(err) return statement diff --git a/tags/parser.go b/tags/parser.go index 15dcaa30..5c94c55b 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -5,10 +5,12 @@ package tags import ( + "encoding/gob" "errors" "fmt" "reflect" "strings" + "sync" "time" "xorm.io/xorm/caches" @@ -25,6 +27,7 @@ type Parser struct { TableMapper names.Mapper handlers map[string]Handler cacherMgr *caches.Manager + tableCache sync.Map // map[reflect.Type]*schemas.Table } func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnMapper names.Mapper, cacherMgr *caches.Manager) *Parser { @@ -51,6 +54,36 @@ func addIndex(indexName string, table *schemas.Table, col *schemas.Column, index } func (parser *Parser) MapType(v reflect.Value) (*schemas.Table, error) { + t := v.Type() + tableI, ok := parser.tableCache.Load(t) + if ok { + return tableI.(*schemas.Table), nil + } + + table, err := parser.mapType(v) + if err != nil { + return nil, err + } + + parser.tableCache.Store(t, table) + + if parser.cacherMgr.GetDefaultCacher() != nil { + if v.CanAddr() { + gob.Register(v.Addr().Interface()) + } else { + gob.Register(v.Interface()) + } + } + + return table, nil +} + +// ClearTable removes the database mapper of a type from the cache +func (parser *Parser) ClearTable(t reflect.Type) { + parser.tableCache.Delete(t) +} + +func (parser *Parser) mapType(v reflect.Value) (*schemas.Table, error) { t := v.Type() table := schemas.NewEmptyTable() table.Type = t diff --git a/tags/tag.go b/tags/tag.go index 3222615a..a043ed77 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -280,7 +280,7 @@ func ExtendsTagHandler(ctx *Context) error { isPtr = true fallthrough case reflect.Struct: - parentTable, err := ctx.parser.MapType(fieldValue) + parentTable, err := ctx.parser.mapType(fieldValue) if err != nil { return err } diff --git a/tags_test.go b/tags_test.go index 2d90948b..b8a43670 100644 --- a/tags_test.go +++ b/tags_test.go @@ -92,12 +92,8 @@ func TestExtends(t *testing.T) { tu9 := &tempUser4{} _, err = testEngine.Get(tu9) assert.NoError(t, err) - - if tu9.TempUser2.TempUser.Username != tu8.TempUser2.TempUser.Username || tu9.TempUser2.Departname != tu8.TempUser2.Departname { - err = errors.New(fmt.Sprintln("not equal for", tu8, tu9)) - t.Error(err) - panic(err) - } + assert.EqualValues(t, tu8.TempUser2.TempUser.Username, tu9.TempUser2.TempUser.Username) + assert.EqualValues(t, tu8.TempUser2.Departname, tu9.TempUser2.Departname) tu10 := &tempUser4{tempUser2{tempUser{0, "extends update"}, ""}} _, err = testEngine.ID(tu9.TempUser2.TempUser.Id).Update(tu10) @@ -117,17 +113,10 @@ func TestExtends(t *testing.T) { _, err = testEngine.Get(tu5) assert.NoError(t, err) - if tu5.Temp == nil { - err = errors.New("error get data extends") - t.Error(err) - panic(err) - } - if tu5.Temp.Id != 1 || tu5.Temp.Username != "extends" || - tu5.Departname != "dev depart" { - err = errors.New("error get data extends") - t.Error(err) - panic(err) - } + assert.NotNil(t, tu5.Temp) + assert.EqualValues(t, 1, tu5.Temp.Id) + assert.EqualValues(t, "extends", tu5.Temp.Username) + assert.EqualValues(t, "dev depart", tu5.Departname) tu6 := &tempUser3{&tempUser{0, "extends update"}, ""} _, err = testEngine.ID(tu5.Temp.Id).Update(tu6) @@ -162,47 +151,25 @@ func TestExtends(t *testing.T) { qt(ui), qt(ud), qt(ui), qt(udid), qt(ud), qt(uiid)) b, err := testEngine.SQL(sql).NoCascade().Get(&info) assert.NoError(t, err) - if !b { - err = errors.New("should has lest one record") - t.Error(err) - panic(err) - } - fmt.Println(info) - if info.Userinfo.Uid == 0 || info.Userdetail.Id == 0 { - err = errors.New("all of the id should has value") - t.Error(err) - panic(err) - } + assert.True(t, b, "should has lest one record") + assert.True(t, info.Userinfo.Uid > 0, "all of the id should has value") + assert.True(t, info.Userdetail.Id > 0, "all of the id should has value") - fmt.Println("----join--info2") var info2 UserAndDetail b, err = testEngine.Table(&Userinfo{}). Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). NoCascade().Get(&info2) - if err != nil { - t.Error(err) - panic(err) - } - if !b { - err = errors.New("should has lest one record") - t.Error(err) - panic(err) - } - if info2.Userinfo.Uid == 0 || info2.Userdetail.Id == 0 { - err = errors.New("all of the id should has value") - t.Error(err) - panic(err) - } - fmt.Println(info2) + assert.NoError(t, err) + assert.True(t, b) + 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") - fmt.Println("----join--infos2") var infos2 = make([]UserAndDetail, 0) err = testEngine.Table(&Userinfo{}). Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)). NoCascade(). Find(&infos2) assert.NoError(t, err) - fmt.Println(infos2) } type MessageBase struct { diff --git a/xorm.go b/xorm.go index f3230aa1..2946b7c9 100644 --- a/xorm.go +++ b/xorm.go @@ -10,9 +10,7 @@ import ( "context" "fmt" "os" - "reflect" "runtime" - "sync" "time" "xorm.io/xorm/caches" @@ -61,14 +59,17 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { return nil, err } + cacherMgr := caches.NewManager() + mapper := names.NewCacheMapper(new(names.SnakeMapper)) + tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) + engine := &Engine{ db: db, dialect: dialect, - Tables: make(map[reflect.Type]*schemas.Table), - mutex: &sync.RWMutex{}, TZLocation: time.Local, defaultContext: context.Background(), - cacherMgr: caches.NewManager(), + cacherMgr: cacherMgr, + tagParser: tagParser, } if uri.DBType == schemas.SQLITE { @@ -80,8 +81,6 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { logger := log.NewSimpleLogger(os.Stdout) logger.SetLevel(log.LOG_INFO) engine.SetLogger(logger) - mapper := names.NewCacheMapper(new(names.SnakeMapper)) - engine.tagParser = tags.NewParser("xorm", dialect, mapper, mapper, engine.cacherMgr) runtime.SetFinalizer(engine, close) From 2b62dc5a51279e1e6752bdba0ae8a2096e6e021b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Feb 2020 12:29:08 +0000 Subject: [PATCH 042/112] Move statement as a sub package (#1564) Fix test Fix bug Move statement as a sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1564 --- context_cache.go => contexts/context_cache.go | 2 +- dialects/dialect.go | 5 + dialects/postgres.go | 4 + dialects/table_name.go | 90 ++ .../table_name_test.go | 12 +- dialects/time.go | 49 + engine.go | 93 +- engine_cond.go | 234 ---- engine_table.go | 109 -- error.go | 2 - helpers.go | 29 - interface.go | 27 +- internal/json/json.go | 31 + internal/statements/cache.go | 79 ++ .../statements/column_map.go | 31 +- .../statements/expr_param.go | 34 +- internal/statements/query.go | 448 ++++++++ .../statements/statement.go | 1024 ++++++----------- .../statements/statement_args.go | 32 +- internal/statements/statement_test.go | 184 +++ types.go => internal/statements/types.go | 2 +- internal/statements/update.go | 280 +++++ internal/utils/name.go | 13 + internal/utils/reflect.go | 13 + internal/utils/sql.go | 19 + internal/utils/strings.go | 30 + rows.go | 9 +- schemas/quote.go | 34 + session.go | 46 +- session_cols.go | 13 - session_cond.go | 2 +- session_convert.go | 21 +- session_delete.go | 12 +- session_exist.go | 80 +- session_find.go | 88 +- session_find_test.go | 9 +- session_get.go | 17 +- session_get_test.go | 5 +- session_insert.go | 80 +- session_iterate.go | 14 +- session_query.go | 72 +- session_raw.go | 18 +- session_schema.go | 26 +- session_stats.go | 31 +- session_tx_test.go | 5 +- session_update.go | 92 +- session_update_test.go | 25 +- statement_test.go | 174 --- tags_test.go | 29 +- types_test.go | 9 +- 50 files changed, 1995 insertions(+), 1792 deletions(-) rename context_cache.go => contexts/context_cache.go (97%) create mode 100644 dialects/table_name.go rename engine_table_test.go => dialects/table_name_test.go (60%) create mode 100644 dialects/time.go delete mode 100644 engine_cond.go delete mode 100644 engine_table.go create mode 100644 internal/json/json.go create mode 100644 internal/statements/cache.go rename statement_columnmap.go => internal/statements/column_map.go (52%) rename statement_exprparam.go => internal/statements/expr_param.go (76%) create mode 100644 internal/statements/query.go rename statement.go => internal/statements/statement.go (55%) rename statement_args.go => internal/statements/statement_args.go (78%) create mode 100644 internal/statements/statement_test.go rename types.go => internal/statements/types.go (94%) create mode 100644 internal/statements/update.go create mode 100644 internal/utils/name.go create mode 100644 internal/utils/reflect.go create mode 100644 internal/utils/sql.go create mode 100644 internal/utils/strings.go diff --git a/context_cache.go b/contexts/context_cache.go similarity index 97% rename from context_cache.go rename to contexts/context_cache.go index 1bc22884..0d0f0f02 100644 --- a/context_cache.go +++ b/contexts/context_cache.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package contexts // ContextCache is the interface that operates the cache data. type ContextCache interface { diff --git a/dialects/dialect.go b/dialects/dialect.go index 26d6521a..e9e512ee 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -41,6 +41,7 @@ type Dialect interface { DBType() DBType SQLType(*schemas.Column) string FormatBytes(b []byte) string + DefaultSchema() string DriverName() string DataSourceName() string @@ -103,6 +104,10 @@ func (b *Base) SetLogger(logger log.Logger) { b.logger = logger } +func (b *Base) DefaultSchema() string { + return "" +} + func (b *Base) Init(db *core.DB, dialect Dialect, uri *URI, drivername, dataSourceName string) error { b.db, b.dialect, b.uri = db, dialect, uri b.driverName, b.dataSourceName = drivername, dataSourceName diff --git a/dialects/postgres.go b/dialects/postgres.go index d6847b02..94514e95 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -788,6 +788,10 @@ func (db *postgres) Init(d *core.DB, uri *URI, drivername, dataSourceName string return nil } +func (db *postgres) DefaultSchema() string { + return PostgresPublicSchema +} + func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { diff --git a/dialects/table_name.go b/dialects/table_name.go new file mode 100644 index 00000000..a989b386 --- /dev/null +++ b/dialects/table_name.go @@ -0,0 +1,90 @@ +// Copyright 2015 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 ( + "fmt" + "reflect" + "strings" + + "xorm.io/xorm/internal/utils" + "xorm.io/xorm/names" +) + +// TableNameWithSchema will add schema prefix on table name if possible +func TableNameWithSchema(dialect Dialect, tableName string) string { + // Add schema name as prefix of table name. + // Only for postgres database. + if dialect.URI().Schema != "" && + dialect.URI().Schema != dialect.DefaultSchema() && + strings.Index(tableName, ".") == -1 { + return fmt.Sprintf("%s.%s", dialect.URI().Schema, tableName) + } + return tableName +} + +// TableNameNoSchema returns table name with given tableName +func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface{}) string { + quote := dialect.Quoter().Quote + switch 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]) + } + case []interface{}: + t := tableName.([]interface{}) + l := len(t) + var table string + if l > 0 { + f := t[0] + switch f.(type) { + case string: + table = f.(string) + case names.TableName: + table = f.(names.TableName).TableName() + default: + v := utils.ReflectValue(f) + t := v.Type() + if t.Kind() == reflect.Struct { + table = names.GetTableName(mapper, v) + } else { + table = quote(fmt.Sprintf("%v", f)) + } + } + } + if l > 1 { + return fmt.Sprintf("%v AS %v", quote(table), quote(fmt.Sprintf("%v", t[1]))) + } else if l == 1 { + return quote(table) + } + case names.TableName: + return tableName.(names.TableName).TableName() + case string: + return tableName.(string) + case reflect.Value: + v := tableName.(reflect.Value) + return names.GetTableName(mapper, v) + default: + v := utils.ReflectValue(tableName) + t := v.Type() + if t.Kind() == reflect.Struct { + return names.GetTableName(mapper, v) + } + return quote(fmt.Sprintf("%v", tableName)) + } + return "" +} + +// FullTableName returns table name with quote and schema according parameter +func FullTableName(dialect Dialect, mapper names.Mapper, bean interface{}, includeSchema ...bool) string { + tbName := TableNameNoSchema(dialect, mapper, bean) + if len(includeSchema) > 0 && includeSchema[0] && !utils.IsSubQuery(tbName) { + tbName = TableNameWithSchema(dialect, tbName) + } + return tbName +} diff --git a/engine_table_test.go b/dialects/table_name_test.go similarity index 60% rename from engine_table_test.go rename to dialects/table_name_test.go index 8f2300aa..66edc2b4 100644 --- a/engine_table_test.go +++ b/dialects/table_name_test.go @@ -2,11 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "testing" + "xorm.io/xorm/names" + "github.com/stretchr/testify/assert" ) @@ -20,9 +22,9 @@ func (mcc *MCC) TableName() string { return "mcc" } -func TestTableName1(t *testing.T) { - assert.NoError(t, prepareEngine()) +func TestFullTableName(t *testing.T) { + dialect := QueryDialect("mysql") - assert.EqualValues(t, "mcc", testEngine.TableName(new(MCC))) - assert.EqualValues(t, "mcc", testEngine.TableName("mcc")) + assert.EqualValues(t, "mcc", FullTableName(dialect, names.SnakeMapper{}, &MCC{})) + assert.EqualValues(t, "mcc", FullTableName(dialect, names.SnakeMapper{}, "mcc")) } diff --git a/dialects/time.go b/dialects/time.go new file mode 100644 index 00000000..022dc960 --- /dev/null +++ b/dialects/time.go @@ -0,0 +1,49 @@ +// Copyright 2015 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 ( + "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. + v = t.Format("2006-01-02 15:04:05") + case schemas.TimeStampz: + if dialect.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 +} + +func FormatColumnTime(dialect Dialect, defaultTimeZone *time.Location, col *schemas.Column, t time.Time) (v interface{}) { + if t.IsZero() { + if col.Nullable { + return nil + } + return "" + } + + if col.TimeZone != nil { + return FormatTime(dialect, col.SQLType.Name, t.In(col.TimeZone)) + } + return FormatTime(dialect, col.SQLType.Name, t.In(defaultTimeZone)) +} diff --git a/engine.go b/engine.go index 1bf42d15..cf0126e9 100644 --- a/engine.go +++ b/engine.go @@ -18,7 +18,6 @@ import ( "strings" "time" - "xorm.io/builder" "xorm.io/xorm/caches" "xorm.io/xorm/core" "xorm.io/xorm/dialects" @@ -65,25 +64,6 @@ func (engine *Engine) BufferSize(size int) *Session { return session.BufferSize(size) } -// CondDeleted returns the conditions whether a record is soft deleted. -func (engine *Engine) CondDeleted(col *schemas.Column) builder.Cond { - var cond = builder.NewCond() - if col.SQLType.IsNumeric() { - cond = builder.Eq{col.Name: 0} - } else { - // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. - if engine.dialect.DBType() != schemas.MSSQL { - cond = builder.Eq{col.Name: utils.ZeroTime1} - } - } - - if col.Nullable { - cond = cond.Or(builder.IsNull{col.Name}) - } - - return cond -} - // ShowSQL show SQL statement or not on logger if log level is great than INFO func (engine *Engine) ShowSQL(show ...bool) { engine.logger.ShowSQL(show...) @@ -237,7 +217,7 @@ func (engine *Engine) NoCascade() *Session { // MapCacher Set a table use a special cacher func (engine *Engine) MapCacher(bean interface{}, cacher caches.Cacher) error { - engine.SetCacher(engine.TableName(bean, true), cacher) + engine.SetCacher(dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, true), cacher) return nil } @@ -759,13 +739,13 @@ func (t *Table) IsValid() bool { } // TableInfo get table info according to bean's content -func (engine *Engine) TableInfo(bean interface{}) *Table { - v := rValue(bean) +func (engine *Engine) TableInfo(bean interface{}) (*Table, error) { + v := utils.ReflectValue(bean) tb, err := engine.tagParser.MapType(v) if err != nil { - engine.logger.Error(err) + return nil, err } - return &Table{tb, engine.TableName(bean)} + return &Table{tb, dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean)}, nil } // IsTableEmpty if a table has any reocrd @@ -787,6 +767,11 @@ func (engine *Engine) IDOf(bean interface{}) schemas.PK { return engine.IDOfV(reflect.ValueOf(bean)) } +// TableName returns table name with schema prefix if has +func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string { + return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...) +} + // IDOfV get id from one value of struct func (engine *Engine) IDOfV(rv reflect.Value) schemas.PK { pk, err := engine.idOfV(rv) @@ -873,7 +858,7 @@ func (engine *Engine) CreateUniques(bean interface{}) error { // ClearCacheBean if enabled cache, clear the cache bean func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { - tableName := engine.TableName(bean) + tableName := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean) cacher := engine.GetCacher(tableName) if cacher != nil { cacher.ClearIds(tableName) @@ -885,7 +870,7 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { // ClearCache if enabled cache, clear some tables' cache func (engine *Engine) ClearCache(beans ...interface{}) error { for _, bean := range beans { - tableName := engine.TableName(bean) + tableName := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean) cacher := engine.GetCacher(tableName) if cacher != nil { cacher.ClearIds(tableName) @@ -908,8 +893,8 @@ func (engine *Engine) Sync(beans ...interface{}) error { defer session.Close() for _, bean := range beans { - v := rValue(bean) - tableNameNoSchema := engine.TableName(bean) + v := utils.ReflectValue(bean) + tableNameNoSchema := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean) table, err := engine.tagParser.MapType(v) if err != nil { return err @@ -946,7 +931,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } if !isExist { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } err = session.addColumn(col.Name) @@ -957,7 +942,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } for name, index := range table.Indexes { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } if index.Type == schemas.UniqueType { @@ -966,7 +951,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } if !isExist { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } @@ -981,7 +966,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } if !isExist { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } @@ -1250,45 +1235,11 @@ func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) { if !col.DisableTimeZone && col.TimeZone != nil { tz = col.TimeZone } - return engine.formatTime(col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) + return dialects.FormatTime(engine.dialect, col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) } func (engine *Engine) formatColTime(col *schemas.Column, t time.Time) (v interface{}) { - if t.IsZero() { - if col.Nullable { - return nil - } - return "" - } - - if col.TimeZone != nil { - return engine.formatTime(col.SQLType.Name, t.In(col.TimeZone)) - } - return engine.formatTime(col.SQLType.Name, t.In(engine.DatabaseTZ)) -} - -// formatTime format time as column type -func (engine *Engine) formatTime(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. - v = t.Format("2006-01-02 15:04:05") - case schemas.TimeStampz: - if engine.dialect.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 + return dialects.FormatColumnTime(engine.dialect, engine.DatabaseTZ, col, t) } // GetColumnMapper returns the column name mapper @@ -1332,3 +1283,7 @@ func (engine *Engine) Unscoped() *Session { session.isAutoClose = true return session.Unscoped() } + +func (engine *Engine) tbNameWithSchema(v string) string { + return dialects.TableNameWithSchema(engine.dialect, v) +} diff --git a/engine_cond.go b/engine_cond.go deleted file mode 100644 index e757df11..00000000 --- a/engine_cond.go +++ /dev/null @@ -1,234 +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/driver" - "fmt" - "reflect" - "strings" - "time" - - "xorm.io/builder" - "xorm.io/xorm/convert" - "xorm.io/xorm/internal/utils" - "xorm.io/xorm/schemas" -) - -func (engine *Engine) buildConds(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) { - var conds []builder.Cond - for _, col := range table.Columns() { - if !includeVersion && col.IsVersion { - continue - } - if !includeUpdated && col.IsUpdated { - continue - } - if !includeAutoIncr && col.IsAutoIncrement { - continue - } - - if engine.dialect.DBType() == schemas.MSSQL && (col.SQLType.Name == schemas.Text || col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { - continue - } - if col.SQLType.IsJson() { - continue - } - - var colName string - if addedTableName { - var nm = tableName - if len(aliasName) > 0 { - nm = aliasName - } - colName = engine.Quote(nm) + "." + engine.Quote(col.Name) - } else { - colName = engine.Quote(col.Name) - } - - fieldValuePtr, err := col.ValueOf(bean) - if err != nil { - if !strings.Contains(err.Error(), "is not valid") { - engine.logger.Warn(err) - } - continue - } - - if col.IsDeleted && !unscoped { // tag "deleted" is enabled - conds = append(conds, engine.CondDeleted(col)) - } - - fieldValue := *fieldValuePtr - if fieldValue.Interface() == nil { - continue - } - - fieldType := reflect.TypeOf(fieldValue.Interface()) - requiredField := useAllCols - - if b, ok := getFlagForColumn(mustColumnMap, col); ok { - if b { - requiredField = true - } else { - continue - } - } - - if fieldType.Kind() == reflect.Ptr { - if fieldValue.IsNil() { - if includeNil { - conds = append(conds, builder.Eq{colName: nil}) - } - continue - } else if !fieldValue.IsValid() { - continue - } else { - // dereference ptr type to instance type - fieldValue = fieldValue.Elem() - fieldType = reflect.TypeOf(fieldValue.Interface()) - requiredField = true - } - } - - 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 - } - t := int64(fieldValue.Uint()) - val = reflect.ValueOf(&t).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 = engine.formatColTime(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 { - continue - } - } else { - if col.SQLType.IsJson() { - if col.SQLType.IsText() { - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - engine.logger.Error(err) - continue - } - val = string(bytes) - } else if col.SQLType.IsBlob() { - var bytes []byte - var err error - bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - engine.logger.Error(err) - continue - } - val = bytes - } - } else { - table, err := engine.tagParser.MapType(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: - 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 := DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - engine.logger.Error(err) - continue - } - 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 = DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - engine.logger.Error(err) - continue - } - val = bytes - } - } else { - continue - } - default: - val = fieldValue.Interface() - } - - conds = append(conds, builder.Eq{colName: val}) - } - - return builder.And(conds...), nil -} diff --git a/engine_table.go b/engine_table.go deleted file mode 100644 index 0954b2d3..00000000 --- a/engine_table.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 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" - "reflect" - "strings" - - "xorm.io/xorm/dialects" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" -) - -// tbNameWithSchema will automatically add schema prefix on table name -func (engine *Engine) tbNameWithSchema(v string) string { - // Add schema name as prefix of table name. - // Only for postgres database. - if engine.dialect.DBType() == schemas.POSTGRES && - engine.dialect.URI().Schema != "" && - engine.dialect.URI().Schema != dialects.PostgresPublicSchema && - strings.Index(v, ".") == -1 { - return engine.dialect.URI().Schema + "." + v - } - return v -} - -func isSubQuery(tbName string) bool { - const selStr = "select" - if len(tbName) <= len(selStr)+1 { - return false - } - - return strings.EqualFold(tbName[:len(selStr)], selStr) || strings.EqualFold(tbName[:len(selStr)+1], "("+selStr) -} - -// TableName returns table name with schema prefix if has -func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string { - tbName := engine.tbNameNoSchema(bean) - if len(includeSchema) > 0 && includeSchema[0] && !isSubQuery(tbName) { - tbName = engine.tbNameWithSchema(tbName) - } - return tbName -} - -// tbName get some table's table name -func (session *Session) tbNameNoSchema(table *schemas.Table) string { - if len(session.statement.AltTableName) > 0 { - return session.statement.AltTableName - } - - return table.Name -} - -func (engine *Engine) tbNameNoSchema(tablename interface{}) string { - switch tablename.(type) { - case []string: - t := tablename.([]string) - if len(t) > 1 { - return fmt.Sprintf("%v AS %v", engine.Quote(t[0]), engine.Quote(t[1])) - } else if len(t) == 1 { - return engine.Quote(t[0]) - } - case []interface{}: - t := tablename.([]interface{}) - l := len(t) - var table string - if l > 0 { - f := t[0] - switch f.(type) { - case string: - table = f.(string) - case names.TableName: - table = f.(names.TableName).TableName() - default: - v := rValue(f) - t := v.Type() - if t.Kind() == reflect.Struct { - table = names.GetTableName(engine.GetTableMapper(), v) - } else { - table = engine.Quote(fmt.Sprintf("%v", f)) - } - } - } - if l > 1 { - return fmt.Sprintf("%v AS %v", engine.Quote(table), - engine.Quote(fmt.Sprintf("%v", t[1]))) - } else if l == 1 { - return engine.Quote(table) - } - case names.TableName: - return tablename.(names.TableName).TableName() - case string: - return tablename.(string) - case reflect.Value: - v := tablename.(reflect.Value) - return names.GetTableName(engine.GetTableMapper(), v) - default: - v := rValue(tablename) - t := v.Type() - if t.Kind() == reflect.Struct { - return names.GetTableName(engine.GetTableMapper(), v) - } - return engine.Quote(fmt.Sprintf("%v", tablename)) - } - return "" -} diff --git a/error.go b/error.go index 2e9cbfaa..a223fc4a 100644 --- a/error.go +++ b/error.go @@ -26,8 +26,6 @@ var ( ErrNotImplemented = errors.New("Not implemented") // ErrConditionType condition type unsupported ErrConditionType = errors.New("Unsupported condition type") - // ErrUnSupportedSQLType parameter of SQL is not supported - ErrUnSupportedSQLType = errors.New("Unsupported sql type") ) // ErrFieldIsNotExist columns does not exist diff --git a/helpers.go b/helpers.go index 1401cbf2..e2158c24 100644 --- a/helpers.go +++ b/helpers.go @@ -9,7 +9,6 @@ import ( "fmt" "reflect" "strconv" - "strings" "time" ) @@ -138,26 +137,6 @@ func int64ToInt(id int64, tp reflect.Type) interface{} { return int64ToIntValue(id, tp).Interface() } -func indexNoCase(s, sep string) int { - return strings.Index(strings.ToLower(s), strings.ToLower(sep)) -} - -func splitNoCase(s, sep string) []string { - idx := indexNoCase(s, sep) - if idx < 0 { - return []string{s} - } - return strings.Split(s, s[idx:idx+len(sep)]) -} - -func splitNNoCase(s, sep string, n int) []string { - idx := indexNoCase(s, sep) - if idx < 0 { - return []string{s} - } - return strings.SplitN(s, s[idx:idx+len(sep)], n) -} - func makeArray(elem string, count int) []string { res := make([]string, count) for i := 0; i < count; i++ { @@ -166,10 +145,6 @@ func makeArray(elem string, count int) []string { return res } -func rValue(bean interface{}) reflect.Value { - return reflect.Indirect(reflect.ValueOf(bean)) -} - func rType(bean interface{}) reflect.Type { sliceValue := reflect.Indirect(reflect.ValueOf(bean)) // return reflect.TypeOf(sliceValue.Interface()) @@ -183,10 +158,6 @@ func structName(v reflect.Type) string { return v.Name() } -func indexName(tableName, idxName string) string { - return fmt.Sprintf("IDX_%v_%v", tableName, idxName) -} - func formatTime(t time.Time) string { return t.Format("2006-01-02 15:04:05") } diff --git a/interface.go b/interface.go index d7e5b778..e7894012 100644 --- a/interface.go +++ b/interface.go @@ -7,7 +7,6 @@ package xorm import ( "context" "database/sql" - "encoding/json" "reflect" "time" @@ -113,7 +112,7 @@ type EngineInterface interface { Sync(...interface{}) error Sync2(...interface{}) error StoreEngine(storeEngine string) *Session - TableInfo(bean interface{}) *Table + TableInfo(bean interface{}) (*Table, error) TableName(interface{}, ...bool) string UnMapType(reflect.Type) } @@ -123,27 +122,3 @@ var ( _ EngineInterface = &Engine{} _ EngineInterface = &EngineGroup{} ) - -// JSONInterface represents an interface to handle json data -type JSONInterface interface { - Marshal(v interface{}) ([]byte, error) - Unmarshal(data []byte, v interface{}) error -} - -var ( - // DefaultJSONHandler default json handler - DefaultJSONHandler JSONInterface = StdJSON{} -) - -// StdJSON implements JSONInterface via encoding/json -type StdJSON struct{} - -// Marshal implements JSONInterface -func (StdJSON) Marshal(v interface{}) ([]byte, error) { - return json.Marshal(v) -} - -// Unmarshal implements JSONInterface -func (StdJSON) Unmarshal(data []byte, v interface{}) error { - return json.Unmarshal(data, v) -} diff --git a/internal/json/json.go b/internal/json/json.go new file mode 100644 index 00000000..c9a2eb4e --- /dev/null +++ b/internal/json/json.go @@ -0,0 +1,31 @@ +// 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 json + +import "encoding/json" + +// JSONInterface represents an interface to handle json data +type JSONInterface interface { + Marshal(v interface{}) ([]byte, error) + Unmarshal(data []byte, v interface{}) error +} + +var ( + // DefaultJSONHandler default json handler + DefaultJSONHandler JSONInterface = StdJSON{} +) + +// StdJSON implements JSONInterface via encoding/json +type StdJSON struct{} + +// Marshal implements JSONInterface +func (StdJSON) Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +// Unmarshal implements JSONInterface +func (StdJSON) Unmarshal(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} diff --git a/internal/statements/cache.go b/internal/statements/cache.go new file mode 100644 index 00000000..d7f72318 --- /dev/null +++ b/internal/statements/cache.go @@ -0,0 +1,79 @@ +// 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/xorm/internal/utils" + "xorm.io/xorm/schemas" +) + +func (statement *Statement) ConvertIDSQL(sqlStr string) string { + if statement.RefTable != nil { + cols := statement.RefTable.PKColumns() + if len(cols) == 0 { + return "" + } + + colstrs := statement.joinColumns(cols, false) + sqls := utils.SplitNNoCase(sqlStr, " from ", 2) + if len(sqls) != 2 { + return "" + } + + var top string + pLimitN := statement.LimitN + if pLimitN != nil && statement.dialect.DBType() == schemas.MSSQL { + top = fmt.Sprintf("TOP %d ", *pLimitN) + } + + newsql := fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1]) + return newsql + } + return "" +} + +func (statement *Statement) ConvertUpdateSQL(sqlStr string) (string, string) { + if statement.RefTable == nil || len(statement.RefTable.PrimaryKeys) != 1 { + return "", "" + } + + colstrs := statement.joinColumns(statement.RefTable.PKColumns(), true) + sqls := utils.SplitNNoCase(sqlStr, "where", 2) + if len(sqls) != 2 { + if len(sqls) == 1 { + return sqls[0], fmt.Sprintf("SELECT %v FROM %v", + colstrs, statement.quote(statement.TableName())) + } + return "", "" + } + + var whereStr = sqls[1] + + // TODO: for postgres only, if any other database? + var paraStr string + if statement.dialect.DBType() == schemas.POSTGRES { + paraStr = "$" + } else if statement.dialect.DBType() == schemas.MSSQL { + paraStr = ":" + } + + if paraStr != "" { + if strings.Contains(sqls[1], paraStr) { + dollers := strings.Split(sqls[1], paraStr) + whereStr = dollers[0] + for i, c := range dollers[1:] { + ccs := strings.SplitN(c, " ", 2) + whereStr += fmt.Sprintf(paraStr+"%v %v", i+1, ccs[1]) + } + } + } + + return sqls[0], fmt.Sprintf("SELECT %v FROM %v WHERE %v", + colstrs, statement.quote(statement.TableName()), + whereStr) +} diff --git a/statement_columnmap.go b/internal/statements/column_map.go similarity index 52% rename from statement_columnmap.go rename to internal/statements/column_map.go index b6523b1e..8440f821 100644 --- a/statement_columnmap.go +++ b/internal/statements/column_map.go @@ -2,13 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package statements -import "strings" +import ( + "strings" + + "xorm.io/xorm/schemas" +) type columnMap []string -func (m columnMap) contain(colName string) bool { +func (m columnMap) Contain(colName string) bool { if len(m) == 0 { return false } @@ -27,9 +31,28 @@ func (m columnMap) contain(colName string) bool { } func (m *columnMap) add(colName string) bool { - if m.contain(colName) { + if m.Contain(colName) { return false } *m = append(*m, colName) return true } + +func getFlagForColumn(m map[string]bool, col *schemas.Column) (val bool, has bool) { + if len(m) == 0 { + return false, false + } + + n := len(col.Name) + + for mk := range m { + if len(mk) != n { + continue + } + if strings.EqualFold(mk, col.Name) { + return m[mk], true + } + } + + return false, false +} diff --git a/statement_exprparam.go b/internal/statements/expr_param.go similarity index 76% rename from statement_exprparam.go rename to internal/statements/expr_param.go index 3231f86a..6657408e 100644 --- a/statement_exprparam.go +++ b/internal/statements/expr_param.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package statements import ( "fmt" @@ -26,21 +26,21 @@ type exprParam struct { } type exprParams struct { - colNames []string - args []interface{} + ColNames []string + Args []interface{} } func (exprs *exprParams) Len() int { - return len(exprs.colNames) + return len(exprs.ColNames) } func (exprs *exprParams) addParam(colName string, arg interface{}) { - exprs.colNames = append(exprs.colNames, colName) - exprs.args = append(exprs.args, arg) + exprs.ColNames = append(exprs.ColNames, colName) + exprs.Args = append(exprs.Args, arg) } -func (exprs *exprParams) isColExist(colName string) bool { - for _, name := range exprs.colNames { +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 } @@ -49,16 +49,16 @@ func (exprs *exprParams) isColExist(colName string) bool { } func (exprs *exprParams) getByName(colName string) (exprParam, bool) { - for i, name := range exprs.colNames { + for i, name := range exprs.ColNames { if strings.EqualFold(name, colName) { - return exprParam{name, exprs.args[i]}, true + return exprParam{name, exprs.Args[i]}, true } } return exprParam{}, false } -func (exprs *exprParams) writeArgs(w *builder.BytesWriter) error { - for i, expr := range exprs.args { +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 { @@ -83,7 +83,7 @@ func (exprs *exprParams) writeArgs(w *builder.BytesWriter) error { } w.Append(arg) } - if i != len(exprs.args)-1 { + if i != len(exprs.Args)-1 { if _, err := w.WriteString(","); err != nil { return err } @@ -93,7 +93,7 @@ func (exprs *exprParams) writeArgs(w *builder.BytesWriter) error { } func (exprs *exprParams) writeNameArgs(w *builder.BytesWriter) error { - for i, colName := range exprs.colNames { + for i, colName := range exprs.ColNames { if _, err := w.WriteString(colName); err != nil { return err } @@ -101,7 +101,7 @@ func (exprs *exprParams) writeNameArgs(w *builder.BytesWriter) error { return err } - switch arg := exprs.args[i].(type) { + switch arg := exprs.Args[i].(type) { case *builder.Builder: if _, err := w.WriteString("("); err != nil { return err @@ -113,10 +113,10 @@ func (exprs *exprParams) writeNameArgs(w *builder.BytesWriter) error { return err } default: - w.Append(exprs.args[i]) + w.Append(exprs.Args[i]) } - if i+1 != len(exprs.colNames) { + if i+1 != len(exprs.ColNames) { if _, err := w.WriteString(","); err != nil { return err } diff --git a/internal/statements/query.go b/internal/statements/query.go new file mode 100644 index 00000000..1519cb08 --- /dev/null +++ b/internal/statements/query.go @@ -0,0 +1,448 @@ +// 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 ( + "errors" + "fmt" + "reflect" + "strings" + + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { + if len(sqlOrArgs) > 0 { + return ConvertSQLOrArgs(sqlOrArgs...) + } + + if statement.RawSQL != "" { + return statement.RawSQL, statement.RawParams, nil + } + + if len(statement.TableName()) <= 0 { + return "", nil, ErrTableNotFound + } + + var columnStr = statement.ColumnStr() + if len(statement.SelectStr) > 0 { + columnStr = statement.SelectStr + } else { + if statement.JoinStr == "" { + if columnStr == "" { + if statement.GroupByStr != "" { + columnStr = statement.quoteColumnStr(statement.GroupByStr) + } else { + columnStr = statement.genColumnStr() + } + } + } else { + if columnStr == "" { + if statement.GroupByStr != "" { + columnStr = statement.quoteColumnStr(statement.GroupByStr) + } else { + columnStr = "*" + } + } + } + if columnStr == "" { + columnStr = "*" + } + } + + if err := statement.ProcessIDParam(); err != nil { + return "", nil, err + } + + condSQL, condArgs, err := builder.ToSQL(statement.cond) + if err != nil { + return "", nil, err + } + + args := append(statement.joinArgs, condArgs...) + sqlStr, err := statement.GenSelectSQL(columnStr, condSQL, true, true) + if err != nil { + return "", nil, err + } + // for mssql and use limit + qs := strings.Count(sqlStr, "?") + if len(args)*2 == qs { + args = append(args, args...) + } + + return sqlStr, args, nil +} + +func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) { + if statement.RawSQL != "" { + return statement.RawSQL, statement.RawParams, nil + } + + statement.SetRefBean(bean) + + var sumStrs = make([]string, 0, len(columns)) + for _, colName := range columns { + if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { + colName = statement.quote(colName) + } + sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName)) + } + sumSelect := strings.Join(sumStrs, ", ") + + condSQL, condArgs, err := statement.GenConds(bean) + if err != nil { + return "", nil, err + } + + sqlStr, err := statement.GenSelectSQL(sumSelect, condSQL, true, true) + if err != nil { + return "", nil, err + } + + return sqlStr, append(statement.joinArgs, condArgs...), nil +} + +func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, error) { + v := rValue(bean) + isStruct := v.Kind() == reflect.Struct + if isStruct { + statement.SetRefBean(bean) + } + + var columnStr = statement.ColumnStr() + if len(statement.SelectStr) > 0 { + columnStr = statement.SelectStr + } else { + // TODO: always generate column names, not use * even if join + if len(statement.JoinStr) == 0 { + if len(columnStr) == 0 { + if len(statement.GroupByStr) > 0 { + columnStr = statement.quoteColumnStr(statement.GroupByStr) + } else { + columnStr = statement.genColumnStr() + } + } + } else { + if len(columnStr) == 0 { + if len(statement.GroupByStr) > 0 { + columnStr = statement.quoteColumnStr(statement.GroupByStr) + } + } + } + } + + if len(columnStr) == 0 { + columnStr = "*" + } + + if isStruct { + if err := statement.mergeConds(bean); err != nil { + return "", nil, err + } + } else { + if err := statement.ProcessIDParam(); err != nil { + return "", nil, err + } + } + condSQL, condArgs, err := builder.ToSQL(statement.cond) + if err != nil { + return "", nil, err + } + + sqlStr, err := statement.GenSelectSQL(columnStr, condSQL, true, true) + if err != nil { + return "", nil, err + } + + return sqlStr, append(statement.joinArgs, condArgs...), nil +} + +func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interface{}, error) { + if statement.RawSQL != "" { + return statement.RawSQL, statement.RawParams, nil + } + + var condSQL string + var condArgs []interface{} + var err error + if len(beans) > 0 { + statement.SetRefBean(beans[0]) + condSQL, condArgs, err = statement.GenConds(beans[0]) + } else { + condSQL, condArgs, err = builder.ToSQL(statement.cond) + } + if err != nil { + return "", nil, err + } + + var selectSQL = statement.SelectStr + if len(selectSQL) <= 0 { + if statement.IsDistinct { + selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr()) + } else { + selectSQL = "count(*)" + } + } + sqlStr, err := statement.GenSelectSQL(selectSQL, condSQL, false, false) + if err != nil { + return "", nil, err + } + + return sqlStr, append(statement.joinArgs, condArgs...), nil +} + +func (statement *Statement) GenSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) { + var ( + distinct string + dialect = statement.dialect + quote = statement.quote + fromStr = " FROM " + top, mssqlCondi, whereStr string + ) + if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { + distinct = "DISTINCT " + } + if len(condSQL) > 0 { + whereStr = " WHERE " + condSQL + } + + if dialect.DBType() == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { + fromStr += statement.TableName() + } else { + fromStr += quote(statement.TableName()) + } + + if statement.TableAlias != "" { + if dialect.DBType() == schemas.ORACLE { + fromStr += " " + quote(statement.TableAlias) + } else { + fromStr += " AS " + quote(statement.TableAlias) + } + } + if statement.JoinStr != "" { + fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr) + } + + pLimitN := statement.LimitN + if dialect.DBType() == schemas.MSSQL { + if pLimitN != nil { + LimitNValue := *pLimitN + top = fmt.Sprintf("TOP %d ", LimitNValue) + } + if statement.Start > 0 { + var column string + if len(statement.RefTable.PKColumns()) == 0 { + for _, index := range statement.RefTable.Indexes { + if len(index.Cols) == 1 { + column = index.Cols[0] + break + } + } + if len(column) == 0 { + column = statement.RefTable.ColumnsSeq()[0] + } + } else { + column = statement.RefTable.PKColumns()[0].Name + } + if statement.needTableName() { + if len(statement.TableAlias) > 0 { + column = statement.TableAlias + "." + column + } else { + column = statement.TableName() + "." + column + } + } + + var orderStr string + if needOrderBy && len(statement.OrderStr) > 0 { + orderStr = " ORDER BY " + statement.OrderStr + } + + var groupStr string + if len(statement.GroupByStr) > 0 { + groupStr = " GROUP BY " + statement.GroupByStr + } + 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 { + if len(whereStr) > 0 { + fmt.Fprint(&buf, " AND ", mssqlCondi) + } else { + fmt.Fprint(&buf, " WHERE ", mssqlCondi) + } + } + + if statement.GroupByStr != "" { + fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr) + } + if statement.HavingStr != "" { + fmt.Fprint(&buf, " ", statement.HavingStr) + } + if needOrderBy && statement.OrderStr != "" { + fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) + } + if needLimit { + if dialect.DBType() != schemas.MSSQL && dialect.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) + } + } else if dialect.DBType() == schemas.ORACLE { + if statement.Start != 0 || pLimitN != nil { + oldString := buf.String() + buf.Reset() + rawColStr := columnStr + if rawColStr == "*" { + rawColStr = "at.*" + } + 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()), nil + } + + return buf.String(), nil +} + +func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interface{}, error) { + if statement.RawSQL != "" { + return statement.RawSQL, statement.RawParams, nil + } + + var sqlStr string + var args []interface{} + var joinStr string + var err error + if len(bean) == 0 { + tableName := statement.TableName() + if len(tableName) <= 0 { + return "", nil, ErrTableNotFound + } + + tableName = statement.quote(tableName) + if len(statement.JoinStr) > 0 { + joinStr = statement.JoinStr + } + + if statement.Conds().IsValid() { + condSQL, condArgs, err := builder.ToSQL(statement.Conds()) + if err != nil { + return "", nil, err + } + + if statement.dialect.DBType() == schemas.MSSQL { + sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) + } else if statement.dialect.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) + } + args = condArgs + } else { + if statement.dialect.DBType() == schemas.MSSQL { + sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) + } else if statement.dialect.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) + } + 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]) + if err != nil { + return "", nil, err + } + } + + return sqlStr, args, nil +} + +func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interface{}, error) { + if statement.RawSQL != "" { + return statement.RawSQL, statement.RawParams, nil + } + + var sqlStr string + var args []interface{} + var err error + + if len(statement.TableName()) <= 0 { + return "", nil, ErrTableNotFound + } + + var columnStr = statement.ColumnStr() + if len(statement.SelectStr) > 0 { + columnStr = statement.SelectStr + } else { + if statement.JoinStr == "" { + if columnStr == "" { + if statement.GroupByStr != "" { + columnStr = statement.quoteColumnStr(statement.GroupByStr) + } else { + columnStr = statement.genColumnStr() + } + } + } else { + if columnStr == "" { + if statement.GroupByStr != "" { + columnStr = statement.quoteColumnStr(statement.GroupByStr) + } else { + columnStr = "*" + } + } + } + if columnStr == "" { + columnStr = "*" + } + } + + statement.cond = statement.cond.And(autoCond) + condSQL, condArgs, err := builder.ToSQL(statement.cond) + if err != nil { + return "", nil, err + } + + args = append(statement.joinArgs, condArgs...) + sqlStr, err = statement.GenSelectSQL(columnStr, condSQL, true, true) + if err != nil { + return "", nil, err + } + // for mssql and use limit + qs := strings.Count(sqlStr, "?") + if len(args)*2 == qs { + args = append(args, args...) + } + + return sqlStr, args, nil +} diff --git a/statement.go b/internal/statements/statement.go similarity index 55% rename from statement.go rename to internal/statements/statement.go index 3a823d82..92b1809a 100644 --- a/statement.go +++ b/internal/statements/statement.go @@ -2,27 +2,44 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package statements import ( "database/sql/driver" + "errors" "fmt" "reflect" "strings" "time" "xorm.io/builder" + "xorm.io/xorm/contexts" "xorm.io/xorm/convert" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" + "xorm.io/xorm/tags" +) + +var ( + // ErrConditionType condition type unsupported + ErrConditionType = errors.New("Unsupported condition type") + // ErrUnSupportedSQLType parameter of SQL is not supported + ErrUnSupportedSQLType = errors.New("Unsupported sql type") + // ErrUnSupportedType unsupported error + ErrUnSupportedType = errors.New("Unsupported type error") + // ErrTableNotFound table not found error + ErrTableNotFound = errors.New("Table not found") ) // Statement save all the sql info for executing SQL type Statement struct { - RefTable *schemas.Table - dialect dialects.Dialect - Engine *Engine + RefTable *schemas.Table + dialect dialects.Dialect + //Engine *Engine + defaultTimeZone *time.Location + tagParser *tags.Parser Start int LimitN *int idParam *schemas.PK @@ -31,7 +48,7 @@ type Statement struct { joinArgs []interface{} GroupByStr string HavingStr string - selectStr string + SelectStr string useAllCols bool AltTableName string tableName string @@ -43,36 +60,47 @@ type Statement struct { Charset string UseCache bool UseAutoTime bool - noAutoCondition bool + NoAutoCondition bool IsDistinct bool IsForUpdate bool TableAlias string allUseBool bool - checkVersion bool + CheckVersion bool unscoped bool - columnMap columnMap - omitColumnMap columnMap - mustColumnMap map[string]bool - nullableMap map[string]bool - incrColumns exprParams - decrColumns exprParams - exprColumns exprParams + ColumnMap columnMap + OmitColumnMap columnMap + MustColumnMap map[string]bool + NullableMap map[string]bool + IncrColumns exprParams + DecrColumns exprParams + ExprColumns exprParams cond builder.Cond - bufferSize int - context ContextCache - lastError error + BufferSize int + Context contexts.ContextCache + LastError error } -func newStatement(dialect dialects.Dialect) *Statement { +// NewStatement creates a new statement +func NewStatement(dialect dialects.Dialect, tagParser *tags.Parser, defaultTimeZone *time.Location) *Statement { statement := &Statement{ - dialect: dialect, + dialect: dialect, + tagParser: tagParser, + defaultTimeZone: defaultTimeZone, } statement.Reset() return statement } +func (statement *Statement) SetTableName(tableName string) { + statement.tableName = tableName +} + func (statement *Statement) omitStr() string { - return statement.dialect.Quoter().Join(statement.omitColumnMap, " ,") + return statement.dialect.Quoter().Join(statement.OmitColumnMap, " ,") +} + +func (statement *Statement) SetContextCache(ctxCache contexts.ContextCache) { + statement.Context = ctxCache } // Init reset all the statement's fields @@ -86,8 +114,8 @@ func (statement *Statement) Reset() { statement.joinArgs = make([]interface{}, 0) statement.GroupByStr = "" statement.HavingStr = "" - statement.columnMap = columnMap{} - statement.omitColumnMap = columnMap{} + statement.ColumnMap = columnMap{} + statement.OmitColumnMap = columnMap{} statement.AltTableName = "" statement.tableName = "" statement.idParam = nil @@ -95,31 +123,31 @@ func (statement *Statement) Reset() { statement.RawParams = make([]interface{}, 0) statement.UseCache = true statement.UseAutoTime = true - statement.noAutoCondition = false + statement.NoAutoCondition = false statement.IsDistinct = false statement.IsForUpdate = false statement.TableAlias = "" - statement.selectStr = "" + statement.SelectStr = "" statement.allUseBool = false statement.useAllCols = false - statement.mustColumnMap = make(map[string]bool) - statement.nullableMap = make(map[string]bool) - statement.checkVersion = true + statement.MustColumnMap = make(map[string]bool) + statement.NullableMap = make(map[string]bool) + statement.CheckVersion = true statement.unscoped = false - statement.incrColumns = exprParams{} - statement.decrColumns = exprParams{} - statement.exprColumns = exprParams{} + statement.IncrColumns = exprParams{} + statement.DecrColumns = exprParams{} + statement.ExprColumns = exprParams{} statement.cond = builder.NewCond() - statement.bufferSize = 0 - statement.context = nil - statement.lastError = nil + statement.BufferSize = 0 + statement.Context = nil + statement.LastError = nil } // NoAutoCondition if you do not want convert bean's field as query condition, then use this function -func (statement *Statement) NoAutoCondition(no ...bool) *Statement { - statement.noAutoCondition = true +func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { + statement.NoAutoCondition = true if len(no) > 0 { - statement.noAutoCondition = no[0] + statement.NoAutoCondition = no[0] } return statement } @@ -137,13 +165,13 @@ func (statement *Statement) SQL(query interface{}, args ...interface{}) *Stateme var err error statement.RawSQL, statement.RawParams, err = query.(*builder.Builder).ToSQL() if err != nil { - statement.lastError = err + statement.LastError = err } case string: statement.RawSQL = query.(string) statement.RawParams = args default: - statement.lastError = ErrUnSupportedSQLType + statement.LastError = ErrUnSupportedSQLType } return statement @@ -180,7 +208,7 @@ func (statement *Statement) And(query interface{}, args ...interface{}) *Stateme } } default: - statement.lastError = ErrConditionType + statement.LastError = ErrConditionType } return statement @@ -223,291 +251,30 @@ func (statement *Statement) NotIn(column string, args ...interface{}) *Statement return statement } -func (statement *Statement) setRefValue(v reflect.Value) error { +func (statement *Statement) SetRefValue(v reflect.Value) error { var err error - statement.RefTable, err = statement.Engine.tagParser.MapType(reflect.Indirect(v)) + statement.RefTable, err = statement.tagParser.MapType(reflect.Indirect(v)) if err != nil { return err } - statement.tableName = statement.Engine.TableName(v, true) + statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, v, true) return nil } -func (statement *Statement) setRefBean(bean interface{}) error { +func rValue(bean interface{}) reflect.Value { + return reflect.Indirect(reflect.ValueOf(bean)) +} + +func (statement *Statement) SetRefBean(bean interface{}) error { var err error - statement.RefTable, err = statement.Engine.tagParser.MapType(rValue(bean)) + statement.RefTable, err = statement.tagParser.MapType(rValue(bean)) if err != nil { return err } - statement.tableName = statement.Engine.TableName(bean, true) + statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, bean, true) return nil } -// Auto generating update columnes and values according a struct -func (statement *Statement) buildUpdates(bean interface{}, - includeVersion, includeUpdated, includeNil, - includeAutoIncr, update bool) ([]string, []interface{}) { - engine := statement.Engine - table := statement.RefTable - allUseBool := statement.allUseBool - useAllCols := statement.useAllCols - mustColumnMap := statement.mustColumnMap - nullableMap := statement.nullableMap - columnMap := statement.columnMap - omitColumnMap := statement.omitColumnMap - unscoped := statement.unscoped - - var colNames = make([]string, 0) - var args = make([]interface{}, 0) - for _, col := range table.Columns() { - if !includeVersion && col.IsVersion { - continue - } - if col.IsCreated && !columnMap.contain(col.Name) { - continue - } - if !includeUpdated && col.IsUpdated { - continue - } - if !includeAutoIncr && col.IsAutoIncrement { - continue - } - if col.IsDeleted && !unscoped { - continue - } - if omitColumnMap.contain(col.Name) { - continue - } - if len(columnMap) > 0 && !columnMap.contain(col.Name) { - continue - } - - if col.MapType == schemas.ONLYFROMDB { - continue - } - - if statement.incrColumns.isColExist(col.Name) { - continue - } else if statement.decrColumns.isColExist(col.Name) { - continue - } else if statement.exprColumns.isColExist(col.Name) { - continue - } - - fieldValuePtr, err := col.ValueOf(bean) - if err != nil { - engine.logger.Error(err) - continue - } - - fieldValue := *fieldValuePtr - fieldType := reflect.TypeOf(fieldValue.Interface()) - if fieldType == nil { - continue - } - - requiredField := useAllCols - includeNil := useAllCols - - if b, ok := getFlagForColumn(mustColumnMap, col); ok { - if b { - requiredField = true - } else { - continue - } - } - - // !evalphobia! set fieldValue as nil when column is nullable and zero-value - if b, ok := getFlagForColumn(nullableMap, col); ok { - if b && col.Nullable && utils.IsZero(fieldValue.Interface()) { - var nilValue *int - fieldValue = reflect.ValueOf(nilValue) - fieldType = reflect.TypeOf(fieldValue.Interface()) - includeNil = true - } - } - - var val interface{} - - if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - data, err := structConvert.ToDB() - if err != nil { - engine.logger.Error(err) - } else { - val = data - } - goto APPEND - } - } - - if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { - data, err := structConvert.ToDB() - if err != nil { - engine.logger.Error(err) - } else { - val = data - } - goto APPEND - } - - if fieldType.Kind() == reflect.Ptr { - if fieldValue.IsNil() { - if includeNil { - args = append(args, nil) - colNames = append(colNames, fmt.Sprintf("%v=?", statement.quote(col.Name))) - } - continue - } else if !fieldValue.IsValid() { - continue - } else { - // dereference ptr type to instance type - fieldValue = fieldValue.Elem() - fieldType = reflect.TypeOf(fieldValue.Interface()) - requiredField = true - } - } - - 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 - } - t := int64(fieldValue.Uint()) - val = reflect.ValueOf(&t).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 = engine.formatColTime(col, t) - } else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok { - val, _ = nulType.Value() - } else { - if !col.SQLType.IsJson() { - table, err := engine.tagParser.MapType(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.IsValid() && (!requiredField && !utils.IsZero(pkField.Interface())) { - val = pkField.Interface() - } else { - continue - } - } else { - // TODO: how to handler? - panic("not supported") - } - } - } else { - // Blank struct could not be as update data - if requiredField || !utils.IsStructZero(fieldValue) { - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - panic(fmt.Sprintf("mashal %v failed", fieldValue.Interface())) - } - if col.SQLType.IsText() { - val = string(bytes) - } else if col.SQLType.IsBlob() { - val = bytes - } - } else { - continue - } - } - } - case reflect.Array, reflect.Slice, reflect.Map: - if !requiredField { - if fieldValue == reflect.Zero(fieldType) { - continue - } - if fieldType.Kind() == reflect.Array { - if utils.IsArrayZero(fieldValue) { - continue - } - } else if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 { - continue - } - } - - if col.SQLType.IsText() { - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - engine.logger.Error(err) - continue - } - val = string(bytes) - } else if col.SQLType.IsBlob() { - var bytes []byte - var err error - if fieldType.Kind() == reflect.Slice && - fieldType.Elem().Kind() == reflect.Uint8 { - if fieldValue.Len() > 0 { - val = fieldValue.Bytes() - } else { - continue - } - } else if fieldType.Kind() == reflect.Array && - fieldType.Elem().Kind() == reflect.Uint8 { - val = fieldValue.Slice(0, 0).Interface() - } else { - bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - engine.logger.Error(err) - continue - } - val = bytes - } - } else { - continue - } - default: - val = fieldValue.Interface() - } - - APPEND: - args = append(args, val) - if col.IsPrimaryKey { - continue - } - colNames = append(colNames, fmt.Sprintf("%v = ?", statement.quote(col.Name))) - } - - return colNames, args -} - func (statement *Statement) needTableName() bool { return len(statement.JoinStr) > 0 } @@ -563,9 +330,9 @@ func (statement *Statement) ID(id interface{}) *Statement { // 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.addParam(column, arg[0]) } else { - statement.incrColumns.addParam(column, 1) + statement.IncrColumns.addParam(column, 1) } return statement } @@ -573,16 +340,16 @@ 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.addParam(column, arg[0]) } else { - statement.decrColumns.addParam(column, 1) + statement.DecrColumns.addParam(column, 1) } return statement } // SetExpr Generate "Update ... Set column = {expression}" statement func (statement *Statement) SetExpr(column string, expression interface{}) *Statement { - statement.exprColumns.addParam(column, expression) + statement.ExprColumns.addParam(column, expression) return statement } @@ -601,21 +368,34 @@ func (statement *Statement) ForUpdate() *Statement { // Select replace select func (statement *Statement) Select(str string) *Statement { - statement.selectStr = str + statement.SelectStr = 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) + statement.ColumnMap.add(nc) } return statement } -func (statement *Statement) columnStr() string { - return statement.dialect.Quoter().Join(statement.columnMap, ", ") +func (statement *Statement) ColumnStr() string { + return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") } // AllCols update use only: update all columns @@ -628,7 +408,7 @@ func (statement *Statement) AllCols() *Statement { func (statement *Statement) MustCols(columns ...string) *Statement { newColumns := col2NewCols(columns...) for _, nc := range newColumns { - statement.mustColumnMap[strings.ToLower(nc)] = true + statement.MustColumnMap[strings.ToLower(nc)] = true } return statement } @@ -647,7 +427,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement { func (statement *Statement) Omit(columns ...string) { newColumns := col2NewCols(columns...) for _, nc := range newColumns { - statement.omitColumnMap = append(statement.omitColumnMap, nc) + statement.OmitColumnMap = append(statement.OmitColumnMap, nc) } } @@ -655,7 +435,7 @@ func (statement *Statement) Omit(columns ...string) { func (statement *Statement) Nullable(columns ...string) { newColumns := col2NewCols(columns...) for _, nc := range newColumns { - statement.nullableMap[strings.ToLower(nc)] = true + statement.NullableMap[strings.ToLower(nc)] = true } } @@ -717,21 +497,24 @@ func (statement *Statement) Asc(colNames ...string) *Statement { return statement } +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 -func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { +func (statement *Statement) SetTable(tableNameOrBean interface{}) error { v := rValue(tableNameOrBean) t := v.Type() if t.Kind() == reflect.Struct { var err error - statement.RefTable, err = statement.Engine.tagParser.MapType(v) + statement.RefTable, err = statement.tagParser.MapType(v) if err != nil { - statement.Engine.logger.Error(err) - return statement + return err } } - statement.AltTableName = statement.Engine.TableName(tableNameOrBean, true) - return statement + statement.AltTableName = dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, tableNameOrBean, true) + return nil } // Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN @@ -747,7 +530,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition case builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() if err != nil { - statement.lastError = err + statement.LastError = err return statement } @@ -760,7 +543,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition case *builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() if err != nil { - statement.lastError = err + statement.LastError = err return statement } @@ -771,8 +554,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition fmt.Fprintf(&buf, "(%s) %s ON %v", subSQL, aliasName, condition) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) default: - tbName := statement.Engine.TableName(tablename, true) - if !isSubQuery(tbName) { + tbName := dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, tablename, true) + if !utils.IsSubQuery(tbName) { var buf strings.Builder statement.dialect.Quoter().QuoteTo(&buf, tbName) tbName = buf.String() @@ -785,6 +568,15 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition return statement } +// tbName 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 = keys @@ -798,11 +590,15 @@ func (statement *Statement) Having(conditions string) *Statement { } // Unscoped always disable struct tag "deleted" -func (statement *Statement) Unscoped() *Statement { +func (statement *Statement) SetUnscoped() *Statement { statement.unscoped = true return statement } +func (statement *Statement) GetUnscoped() bool { + return statement.unscoped +} + func (statement *Statement) genColumnStr() string { if statement.RefTable == nil { return "" @@ -812,11 +608,11 @@ func (statement *Statement) genColumnStr() string { columns := statement.RefTable.Columns() for _, col := range columns { - if statement.omitColumnMap.contain(col.Name) { + if statement.OmitColumnMap.Contain(col.Name) { continue } - if len(statement.columnMap) > 0 && !statement.columnMap.contain(col.Name) { + if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) { continue } @@ -844,12 +640,12 @@ func (statement *Statement) genColumnStr() string { return buf.String() } -func (statement *Statement) genCreateTableSQL() string { +func (statement *Statement) GenCreateTableSQL() string { return statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName(), statement.StoreEngine, statement.Charset) } -func (statement *Statement) genIndexSQL() []string { +func (statement *Statement) GenIndexSQL() []string { var sqls []string tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { @@ -865,7 +661,7 @@ func uniqueName(tableName, uqeName string) string { return fmt.Sprintf("UQE_%v_%v", tableName, uqeName) } -func (statement *Statement) genUniqueSQL() []string { +func (statement *Statement) GenUniqueSQL() []string { var sqls []string tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { @@ -877,7 +673,7 @@ func (statement *Statement) genUniqueSQL() []string { return sqls } -func (statement *Statement) genDelIndexSQL() []string { +func (statement *Statement) GenDelIndexSQL() []string { var sqls []string tbName := statement.TableName() idx := strings.Index(tbName, ".") @@ -891,9 +687,9 @@ func (statement *Statement) genDelIndexSQL() []string { if index.Type == schemas.UniqueType { rIdxName = uniqueName(idxPrefixName, idxName) } else if index.Type == schemas.IndexType { - rIdxName = indexName(idxPrefixName, idxName) + rIdxName = utils.IndexName(idxPrefixName, idxName) } - sql := fmt.Sprintf("DROP INDEX %v", statement.quote(statement.Engine.TableName(rIdxName, true))) + sql := fmt.Sprintf("DROP INDEX %v", statement.quote(dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, rIdxName, true))) if statement.dialect.IndexOnTable() { sql += fmt.Sprintf(" ON %v", statement.quote(tbName)) } @@ -902,28 +698,240 @@ func (statement *Statement) genDelIndexSQL() []string { return sqls } -func (statement *Statement) buildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { - return statement.Engine.buildConds(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, - statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) +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) { + var conds []builder.Cond + for _, col := range table.Columns() { + if !includeVersion && col.IsVersion { + continue + } + if !includeUpdated && col.IsUpdated { + continue + } + if !includeAutoIncr && col.IsAutoIncrement { + continue + } + + if statement.dialect.DBType() == schemas.MSSQL && (col.SQLType.Name == schemas.Text || col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { + continue + } + if col.SQLType.IsJson() { + continue + } + + var colName string + if addedTableName { + var nm = tableName + if len(aliasName) > 0 { + nm = aliasName + } + colName = statement.quote(nm) + "." + statement.quote(col.Name) + } else { + colName = statement.quote(col.Name) + } + + fieldValuePtr, err := col.ValueOf(bean) + if err != nil { + if !strings.Contains(err.Error(), "is not valid") { + //engine.logger.Warn(err) + } + continue + } + + if col.IsDeleted && !unscoped { // tag "deleted" is enabled + conds = append(conds, statement.CondDeleted(col)) + } + + fieldValue := *fieldValuePtr + if fieldValue.Interface() == nil { + continue + } + + fieldType := reflect.TypeOf(fieldValue.Interface()) + requiredField := useAllCols + + if b, ok := getFlagForColumn(mustColumnMap, col); ok { + if b { + requiredField = true + } else { + continue + } + } + + if fieldType.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + if includeNil { + conds = append(conds, builder.Eq{colName: nil}) + } + continue + } else if !fieldValue.IsValid() { + continue + } else { + // dereference ptr type to instance type + fieldValue = fieldValue.Elem() + fieldType = reflect.TypeOf(fieldValue.Interface()) + requiredField = true + } + } + + 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 + } + t := int64(fieldValue.Uint()) + val = reflect.ValueOf(&t).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 { + continue + } + } else { + if col.SQLType.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.MapType(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: + 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}) + } + + return builder.And(conds...), nil +} + +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) } func (statement *Statement) mergeConds(bean interface{}) error { - if !statement.noAutoCondition { + if !statement.NoAutoCondition { var addedTableName = (len(statement.JoinStr) > 0) - autoCond, err := statement.buildConds(statement.RefTable, bean, true, true, false, true, addedTableName) + autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName) if err != nil { return err } statement.cond = statement.cond.And(autoCond) } - if err := statement.processIDParam(); err != nil { + if err := statement.ProcessIDParam(); err != nil { return err } return nil } -func (statement *Statement) genConds(bean interface{}) (string, []interface{}, error) { +func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, error) { if err := statement.mergeConds(bean); err != nil { return "", nil, err } @@ -936,242 +944,21 @@ func (statement *Statement) quoteColumnStr(columnStr string) string { return statement.dialect.Quoter().Join(columns, ",") } -func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, error) { - v := rValue(bean) - isStruct := v.Kind() == reflect.Struct - if isStruct { - statement.setRefBean(bean) +func ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { + switch sqlOrArgs[0].(type) { + case string: + return sqlOrArgs[0].(string), sqlOrArgs[1:], nil + case *builder.Builder: + return sqlOrArgs[0].(*builder.Builder).ToSQL() + case builder.Builder: + bd := sqlOrArgs[0].(builder.Builder) + return bd.ToSQL() } - var columnStr = statement.columnStr() - if len(statement.selectStr) > 0 { - columnStr = statement.selectStr - } else { - // TODO: always generate column names, not use * even if join - if len(statement.JoinStr) == 0 { - if len(columnStr) == 0 { - if len(statement.GroupByStr) > 0 { - columnStr = statement.quoteColumnStr(statement.GroupByStr) - } else { - columnStr = statement.genColumnStr() - } - } - } else { - if len(columnStr) == 0 { - if len(statement.GroupByStr) > 0 { - columnStr = statement.quoteColumnStr(statement.GroupByStr) - } - } - } - } - - if len(columnStr) == 0 { - columnStr = "*" - } - - if isStruct { - if err := statement.mergeConds(bean); err != nil { - return "", nil, err - } - } else { - if err := statement.processIDParam(); err != nil { - return "", nil, err - } - } - condSQL, condArgs, err := builder.ToSQL(statement.cond) - if err != nil { - return "", nil, err - } - - sqlStr, err := statement.genSelectSQL(columnStr, condSQL, true, true) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil + return "", nil, ErrUnSupportedType } -func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interface{}, error) { - var condSQL string - var condArgs []interface{} - var err error - if len(beans) > 0 { - statement.setRefBean(beans[0]) - condSQL, condArgs, err = statement.genConds(beans[0]) - } else { - condSQL, condArgs, err = builder.ToSQL(statement.cond) - } - if err != nil { - return "", nil, err - } - - var selectSQL = statement.selectStr - if len(selectSQL) <= 0 { - if statement.IsDistinct { - selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.columnStr()) - } else { - selectSQL = "count(*)" - } - } - sqlStr, err := statement.genSelectSQL(selectSQL, condSQL, false, false) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil -} - -func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) { - statement.setRefBean(bean) - - var sumStrs = make([]string, 0, len(columns)) - for _, colName := range columns { - if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { - colName = statement.quote(colName) - } - sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName)) - } - sumSelect := strings.Join(sumStrs, ", ") - - condSQL, condArgs, err := statement.genConds(bean) - if err != nil { - return "", nil, err - } - - sqlStr, err := statement.genSelectSQL(sumSelect, condSQL, true, true) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil -} - -func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) { - var ( - distinct string - dialect = statement.dialect - quote = statement.quote - fromStr = " FROM " - top, mssqlCondi, whereStr string - ) - if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { - distinct = "DISTINCT " - } - if len(condSQL) > 0 { - whereStr = " WHERE " + condSQL - } - - if dialect.DBType() == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { - fromStr += statement.TableName() - } else { - fromStr += quote(statement.TableName()) - } - - if statement.TableAlias != "" { - if dialect.DBType() == schemas.ORACLE { - fromStr += " " + quote(statement.TableAlias) - } else { - fromStr += " AS " + quote(statement.TableAlias) - } - } - if statement.JoinStr != "" { - fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr) - } - - pLimitN := statement.LimitN - if dialect.DBType() == schemas.MSSQL { - if pLimitN != nil { - LimitNValue := *pLimitN - top = fmt.Sprintf("TOP %d ", LimitNValue) - } - if statement.Start > 0 { - var column string - if len(statement.RefTable.PKColumns()) == 0 { - for _, index := range statement.RefTable.Indexes { - if len(index.Cols) == 1 { - column = index.Cols[0] - break - } - } - if len(column) == 0 { - column = statement.RefTable.ColumnsSeq()[0] - } - } else { - column = statement.RefTable.PKColumns()[0].Name - } - if statement.needTableName() { - if len(statement.TableAlias) > 0 { - column = statement.TableAlias + "." + column - } else { - column = statement.TableName() + "." + column - } - } - - var orderStr string - if needOrderBy && len(statement.OrderStr) > 0 { - orderStr = " ORDER BY " + statement.OrderStr - } - - var groupStr string - if len(statement.GroupByStr) > 0 { - groupStr = " GROUP BY " + statement.GroupByStr - } - 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 { - if len(whereStr) > 0 { - fmt.Fprint(&buf, " AND ", mssqlCondi) - } else { - fmt.Fprint(&buf, " WHERE ", mssqlCondi) - } - } - - if statement.GroupByStr != "" { - fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr) - } - if statement.HavingStr != "" { - fmt.Fprint(&buf, " ", statement.HavingStr) - } - if needOrderBy && statement.OrderStr != "" { - fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) - } - if needLimit { - if dialect.DBType() != schemas.MSSQL && dialect.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) - } - } else if dialect.DBType() == schemas.ORACLE { - if statement.Start != 0 || pLimitN != nil { - oldString := buf.String() - buf.Reset() - rawColStr := columnStr - if rawColStr == "*" { - rawColStr = "at.*" - } - 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()), nil - } - - return buf.String(), nil -} - -func (statement *Statement) processIDParam() error { +func (statement *Statement) ProcessIDParam() error { if statement.idParam == nil || statement.RefTable == nil { return nil } @@ -1203,68 +990,21 @@ func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName return strings.Join(colnames, ", ") } -func (statement *Statement) convertIDSQL(sqlStr string) string { - if statement.RefTable != nil { - cols := statement.RefTable.PKColumns() - if len(cols) == 0 { - return "" +// CondDeleted returns the conditions whether a record is soft deleted. +func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { + var cond = builder.NewCond() + if col.SQLType.IsNumeric() { + cond = builder.Eq{col.Name: 0} + } else { + // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. + if statement.dialect.DBType() != schemas.MSSQL { + cond = builder.Eq{col.Name: utils.ZeroTime1} } - - colstrs := statement.joinColumns(cols, false) - sqls := splitNNoCase(sqlStr, " from ", 2) - if len(sqls) != 2 { - return "" - } - - var top string - pLimitN := statement.LimitN - if pLimitN != nil && statement.dialect.DBType() == schemas.MSSQL { - top = fmt.Sprintf("TOP %d ", *pLimitN) - } - - newsql := fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1]) - return newsql } - return "" -} - -func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { - if statement.RefTable == nil || len(statement.RefTable.PrimaryKeys) != 1 { - return "", "" - } - - colstrs := statement.joinColumns(statement.RefTable.PKColumns(), true) - sqls := splitNNoCase(sqlStr, "where", 2) - if len(sqls) != 2 { - if len(sqls) == 1 { - return sqls[0], fmt.Sprintf("SELECT %v FROM %v", - colstrs, statement.quote(statement.TableName())) - } - return "", "" - } - - var whereStr = sqls[1] - - // TODO: for postgres only, if any other database? - var paraStr string - if statement.dialect.DBType() == schemas.POSTGRES { - paraStr = "$" - } else if statement.dialect.DBType() == schemas.MSSQL { - paraStr = ":" - } - - if paraStr != "" { - if strings.Contains(sqls[1], paraStr) { - dollers := strings.Split(sqls[1], paraStr) - whereStr = dollers[0] - for i, c := range dollers[1:] { - ccs := strings.SplitN(c, " ", 2) - whereStr += fmt.Sprintf(paraStr+"%v %v", i+1, ccs[1]) - } - } - } - - return sqls[0], fmt.Sprintf("SELECT %v FROM %v WHERE %v", - colstrs, statement.quote(statement.TableName()), - whereStr) + + if col.Nullable { + cond = cond.Or(builder.IsNull{col.Name}) + } + + return cond } diff --git a/statement_args.go b/internal/statements/statement_args.go similarity index 78% rename from statement_args.go rename to internal/statements/statement_args.go index 22bfeb7b..8eee246e 100644 --- a/statement_args.go +++ b/internal/statements/statement_args.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package statements import ( "fmt" @@ -77,7 +77,7 @@ func convertArg(arg interface{}, convertFunc func(string) string) string { const insertSelectPlaceHolder = true -func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) error { +func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case bool: if statement.dialect.DBType() == schemas.MSSQL { @@ -130,9 +130,9 @@ func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) er return nil } -func (statement *Statement) writeArgs(w *builder.BytesWriter, args []interface{}) error { +func (statement *Statement) WriteArgs(w *builder.BytesWriter, args []interface{}) error { for i, arg := range args { - if err := statement.writeArg(w, arg); err != nil { + if err := statement.WriteArg(w, arg); err != nil { return err } @@ -144,27 +144,3 @@ func (statement *Statement) writeArgs(w *builder.BytesWriter, args []interface{} } return nil } - -func writeStrings(w *builder.BytesWriter, cols []string, leftQuote, rightQuote string) error { - for i, colName := range cols { - if len(leftQuote) > 0 && colName[0] != '`' { - if _, err := w.WriteString(leftQuote); err != nil { - return err - } - } - if _, err := w.WriteString(colName); err != nil { - return err - } - if len(rightQuote) > 0 && colName[len(colName)-1] != '`' { - if _, err := w.WriteString(rightQuote); err != nil { - return err - } - } - if i+1 != len(cols) { - if _, err := w.WriteString(","); err != nil { - return err - } - } - } - return nil -} diff --git a/internal/statements/statement_test.go b/internal/statements/statement_test.go new file mode 100644 index 00000000..3b6e3ae2 --- /dev/null +++ b/internal/statements/statement_test.go @@ -0,0 +1,184 @@ +// 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 statements + +import ( + "reflect" + "strings" + "testing" + + "xorm.io/xorm/schemas" +) + +var colStrTests = []struct { + omitColumn string + onlyToDBColumnNdx int + expected string +}{ + {"", -1, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, + {"Code2", -1, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, + {"", 1, "`ID`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, + {"Code3", 1, "`ID`, `Caption`, `Code1`, `Code2`, `ParentID`, `Latitude`, `Longitude`"}, + {"Longitude", 1, "`ID`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`"}, + {"", 8, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`"}, +} + +func TestColumnsStringGeneration(t *testing.T) { + if dbType == "postgres" || dbType == "mssql" { + return + } + + var statement *Statement + + for ndx, testCase := range colStrTests { + statement = createTestStatement() + + if testCase.omitColumn != "" { + statement.Omit(testCase.omitColumn) + } + + columns := statement.RefTable.Columns() + if testCase.onlyToDBColumnNdx >= 0 { + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB + } + + actual := statement.genColumnStr() + + if actual != testCase.expected { + t.Errorf("[test #%d] Unexpected columns string:\nwant:\t%s\nhave:\t%s", ndx, testCase.expected, actual) + } + if testCase.onlyToDBColumnNdx >= 0 { + columns[testCase.onlyToDBColumnNdx].MapType = schemas.TWOSIDES + } + } +} + +func BenchmarkColumnsStringGeneration(b *testing.B) { + b.StopTimer() + + statement := createTestStatement() + + testCase := colStrTests[0] + + if testCase.omitColumn != "" { + statement.Omit(testCase.omitColumn) // !nemec784! Column must be skipped + } + + if testCase.onlyToDBColumnNdx >= 0 { + columns := statement.RefTable.Columns() + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB // !nemec784! Column must be skipped + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + actual := statement.genColumnStr() + + if actual != testCase.expected { + b.Errorf("Unexpected columns string:\nwant:\t%s\nhave:\t%s", testCase.expected, actual) + } + } +} + +func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { + + b.StopTimer() + + mapCols := make(map[string]bool) + cols := []*schemas.Column{ + {Name: `ID`}, + {Name: `IsDeleted`}, + {Name: `Caption`}, + {Name: `Code1`}, + {Name: `Code2`}, + {Name: `Code3`}, + {Name: `ParentID`}, + {Name: `Latitude`}, + {Name: `Longitude`}, + } + + for _, col := range cols { + mapCols[strings.ToLower(col.Name)] = true + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + + for _, col := range cols { + + if _, ok := getFlagForColumn(mapCols, col); !ok { + b.Fatal("Unexpected result") + } + } + } +} + +func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { + + b.StopTimer() + + mapCols := make(map[string]bool) + cols := []*schemas.Column{ + {Name: `ID`}, + {Name: `IsDeleted`}, + {Name: `Caption`}, + {Name: `Code1`}, + {Name: `Code2`}, + {Name: `Code3`}, + {Name: `ParentID`}, + {Name: `Latitude`}, + {Name: `Longitude`}, + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + + for _, col := range cols { + + if _, ok := getFlagForColumn(mapCols, col); ok { + b.Fatal("Unexpected result") + } + } + } +} + +type TestType struct { + ID int64 `xorm:"ID PK"` + IsDeleted bool `xorm:"IsDeleted"` + Caption string `xorm:"Caption"` + Code1 string `xorm:"Code1"` + Code2 string `xorm:"Code2"` + Code3 string `xorm:"Code3"` + ParentID int64 `xorm:"ParentID"` + Latitude float64 `xorm:"Latitude"` + Longitude float64 `xorm:"Longitude"` +} + +func (TestType) TableName() string { + return "TestTable" +} + +func createTestStatement() *Statement { + if engine, ok := testEngine.(*Engine); ok { + statement := &Statement{} + statement.Reset() + statement.Engine = engine + statement.dialect = engine.dialect + statement.SetRefValue(reflect.ValueOf(TestType{})) + + return statement + } else if eg, ok := testEngine.(*EngineGroup); ok { + statement := &Statement{} + statement.Reset() + statement.Engine = eg.Engine + statement.dialect = eg.Engine.dialect + statement.SetRefValue(reflect.ValueOf(TestType{})) + + return statement + } + return nil +} diff --git a/types.go b/internal/statements/types.go similarity index 94% rename from types.go rename to internal/statements/types.go index ee725dae..0ff36f35 100644 --- a/types.go +++ b/internal/statements/types.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package statements import ( "reflect" diff --git a/internal/statements/update.go b/internal/statements/update.go new file mode 100644 index 00000000..a5d7ec5a --- /dev/null +++ b/internal/statements/update.go @@ -0,0 +1,280 @@ +// 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 statements + +import ( + "database/sql/driver" + "fmt" + "reflect" + "time" + + "xorm.io/xorm/convert" + "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/json" + "xorm.io/xorm/internal/utils" + "xorm.io/xorm/schemas" +) + +// BuildUpdates auto generating update columnes and values according a struct +func (statement *Statement) BuildUpdates(bean interface{}, + includeVersion, includeUpdated, includeNil, + includeAutoIncr, update bool) ([]string, []interface{}, error) { + //engine := statement.Engine + table := statement.RefTable + allUseBool := statement.allUseBool + useAllCols := statement.useAllCols + mustColumnMap := statement.MustColumnMap + nullableMap := statement.NullableMap + columnMap := statement.ColumnMap + omitColumnMap := statement.OmitColumnMap + unscoped := statement.unscoped + + var colNames = make([]string, 0) + var args = make([]interface{}, 0) + for _, col := range table.Columns() { + if !includeVersion && col.IsVersion { + continue + } + if col.IsCreated && !columnMap.Contain(col.Name) { + continue + } + if !includeUpdated && col.IsUpdated { + continue + } + if !includeAutoIncr && col.IsAutoIncrement { + continue + } + if col.IsDeleted && !unscoped { + continue + } + if omitColumnMap.Contain(col.Name) { + continue + } + if len(columnMap) > 0 && !columnMap.Contain(col.Name) { + continue + } + + if col.MapType == schemas.ONLYFROMDB { + continue + } + + if statement.IncrColumns.IsColExist(col.Name) { + continue + } else if statement.DecrColumns.IsColExist(col.Name) { + continue + } else if statement.ExprColumns.IsColExist(col.Name) { + continue + } + + fieldValuePtr, err := col.ValueOf(bean) + if err != nil { + return nil, nil, err + } + + fieldValue := *fieldValuePtr + fieldType := reflect.TypeOf(fieldValue.Interface()) + if fieldType == nil { + continue + } + + requiredField := useAllCols + includeNil := useAllCols + + if b, ok := getFlagForColumn(mustColumnMap, col); ok { + if b { + requiredField = true + } else { + continue + } + } + + // !evalphobia! set fieldValue as nil when column is nullable and zero-value + if b, ok := getFlagForColumn(nullableMap, col); ok { + if b && col.Nullable && utils.IsZero(fieldValue.Interface()) { + var nilValue *int + fieldValue = reflect.ValueOf(nilValue) + fieldType = reflect.TypeOf(fieldValue.Interface()) + includeNil = true + } + } + + var val interface{} + + if fieldValue.CanAddr() { + if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { + data, err := structConvert.ToDB() + if err != nil { + return nil, nil, err + } + + val = data + goto APPEND + } + } + + if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { + data, err := structConvert.ToDB() + if err != nil { + return nil, nil, err + } + + val = data + goto APPEND + } + + if fieldType.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + if includeNil { + args = append(args, nil) + colNames = append(colNames, fmt.Sprintf("%v=?", statement.quote(col.Name))) + } + continue + } else if !fieldValue.IsValid() { + continue + } else { + // dereference ptr type to instance type + fieldValue = fieldValue.Elem() + fieldType = reflect.TypeOf(fieldValue.Interface()) + requiredField = true + } + } + + 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 + } + t := int64(fieldValue.Uint()) + val = reflect.ValueOf(&t).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 nulType, ok := fieldValue.Interface().(driver.Valuer); ok { + val, _ = nulType.Value() + } else { + if !col.SQLType.IsJson() { + table, err := statement.tagParser.MapType(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.IsValid() && (!requiredField && !utils.IsZero(pkField.Interface())) { + val = pkField.Interface() + } else { + continue + } + } else { + // TODO: how to handler? + panic("not supported") + } + } + } else { + // Blank struct could not be as update data + if requiredField || !utils.IsStructZero(fieldValue) { + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + panic(fmt.Sprintf("mashal %v failed", fieldValue.Interface())) + } + if col.SQLType.IsText() { + val = string(bytes) + } else if col.SQLType.IsBlob() { + val = bytes + } + } else { + continue + } + } + } + case reflect.Array, reflect.Slice, reflect.Map: + if !requiredField { + if fieldValue == reflect.Zero(fieldType) { + continue + } + if fieldType.Kind() == reflect.Array { + if utils.IsArrayZero(fieldValue) { + continue + } + } else 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, nil, err + } + val = string(bytes) + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + if fieldType.Kind() == reflect.Slice && + fieldType.Elem().Kind() == reflect.Uint8 { + if fieldValue.Len() > 0 { + val = fieldValue.Bytes() + } else { + continue + } + } else if fieldType.Kind() == reflect.Array && + fieldType.Elem().Kind() == reflect.Uint8 { + val = fieldValue.Slice(0, 0).Interface() + } else { + bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, nil, err + } + val = bytes + } + } else { + continue + } + default: + val = fieldValue.Interface() + } + + APPEND: + args = append(args, val) + if col.IsPrimaryKey { + continue + } + colNames = append(colNames, fmt.Sprintf("%v = ?", statement.quote(col.Name))) + } + + return colNames, args, nil +} diff --git a/internal/utils/name.go b/internal/utils/name.go new file mode 100644 index 00000000..f5fc3ff7 --- /dev/null +++ b/internal/utils/name.go @@ -0,0 +1,13 @@ +// Copyright 2020 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" +) + +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 new file mode 100644 index 00000000..3dad6bfe --- /dev/null +++ b/internal/utils/reflect.go @@ -0,0 +1,13 @@ +// Copyright 2020 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" +) + +func ReflectValue(bean interface{}) reflect.Value { + return reflect.Indirect(reflect.ValueOf(bean)) +} diff --git a/internal/utils/sql.go b/internal/utils/sql.go new file mode 100644 index 00000000..5e68c4a4 --- /dev/null +++ b/internal/utils/sql.go @@ -0,0 +1,19 @@ +// Copyright 2020 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 ( + "strings" +) + +func IsSubQuery(tbName string) bool { + const selStr = "select" + if len(tbName) <= len(selStr)+1 { + return false + } + + return strings.EqualFold(tbName[:len(selStr)], selStr) || + strings.EqualFold(tbName[:len(selStr)+1], "("+selStr) +} diff --git a/internal/utils/strings.go b/internal/utils/strings.go new file mode 100644 index 00000000..b5dc37b7 --- /dev/null +++ b/internal/utils/strings.go @@ -0,0 +1,30 @@ +// 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 utils + +import ( + "strings" +) + +func IndexNoCase(s, sep string) int { + return strings.Index(strings.ToLower(s), strings.ToLower(sep)) +} + +func SplitNoCase(s, sep string) []string { + idx := IndexNoCase(s, sep) + if idx < 0 { + return []string{s} + } + return strings.Split(s, s[idx:idx+len(sep)]) +} + +func SplitNNoCase(s, sep string, n int) []string { + idx := IndexNoCase(s, sep) + if idx < 0 { + return []string{s} + } + return strings.SplitN(s, s[idx:idx+len(sep)], n) +} + diff --git a/rows.go b/rows.go index b52b889d..e14c9894 100644 --- a/rows.go +++ b/rows.go @@ -10,6 +10,7 @@ import ( "reflect" "xorm.io/xorm/core" + "xorm.io/xorm/internal/utils" ) // Rows rows wrapper a rows to @@ -29,7 +30,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { var args []interface{} var err error - if err = rows.session.statement.setRefBean(bean); err != nil { + if err = rows.session.statement.SetRefBean(bean); err != nil { return nil, err } @@ -38,7 +39,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { } if rows.session.statement.RawSQL == "" { - sqlStr, args, err = rows.session.statement.genGetSQL(bean) + sqlStr, args, err = rows.session.statement.GenGetSQL(bean) if err != nil { return nil, err } @@ -84,7 +85,7 @@ func (rows *Rows) Scan(bean interface{}) error { return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) } - if err := rows.session.statement.setRefBean(bean); err != nil { + if err := rows.session.statement.SetRefBean(bean); err != nil { return err } @@ -98,7 +99,7 @@ func (rows *Rows) Scan(bean interface{}) error { return err } - dataStruct := rValue(bean) + dataStruct := utils.ReflectValue(bean) _, err = rows.session.slice2Bean(scanResults, fields, bean, &dataStruct, rows.session.statement.RefTable) if err != nil { return err diff --git a/schemas/quote.go b/schemas/quote.go index 0e022240..736b774a 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -109,6 +109,40 @@ func (q Quoter) Join(a []string, sep string) string { return b.String() } +func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error { + if len(a) == 0 { + return nil + } + + n := len(sep) * (len(a) - 1) + for i := 0; i < len(a); i++ { + n += len(a[i]) + } + + b.Grow(n) + for i, s := range a { + if i > 0 { + if _, err := b.WriteString(sep); err != nil { + return err + } + } + if q[0] != "" && s != "*" && s[0] != '`' { + if _, err := b.WriteString(q[0]); err != nil { + return err + } + } + if _, err := b.WriteString(strings.TrimSpace(s)); err != nil { + return err + } + if q[1] != "" && s != "*" && s[0] != '`' { + if _, err := b.WriteString(q[1]); err != nil { + return err + } + } + } + return nil +} + func (q Quoter) Strings(s []string) []string { var res = make([]string, 0, len(s)) for _, a := range s { diff --git a/session.go b/session.go index 0b0f56c0..92063882 100644 --- a/session.go +++ b/session.go @@ -14,8 +14,11 @@ import ( "strings" "time" + "xorm.io/xorm/contexts" "xorm.io/xorm/convert" "xorm.io/xorm/core" + "xorm.io/xorm/internal/json" + "xorm.io/xorm/internal/statements" "xorm.io/xorm/schemas" ) @@ -32,7 +35,7 @@ type Session struct { db *core.DB engine *Engine tx *core.Tx - statement Statement + statement *statements.Statement isAutoCommit bool isCommitedOrRollbacked bool isAutoClose bool @@ -73,9 +76,12 @@ func (session *Session) Clone() *Session { // Init reset the session as the init status. func (session *Session) Init() { - session.statement.Reset() - session.statement.dialect = session.engine.dialect - session.statement.Engine = session.engine + session.statement = statements.NewStatement( + session.engine.dialect, + session.engine.tagParser, + session.engine.DatabaseTZ, + ) + session.showSQL = session.engine.showSQL session.isAutoCommit = true session.isCommitedOrRollbacked = false @@ -118,8 +124,8 @@ func (session *Session) Close() { } // ContextCache enable context cache or not -func (session *Session) ContextCache(context ContextCache) *Session { - session.statement.context = context +func (session *Session) ContextCache(context contexts.ContextCache) *Session { + session.statement.SetContextCache(context) return session } @@ -158,7 +164,9 @@ func (session *Session) After(closures func(interface{})) *Session { // Table can input a string or pointer to struct for special a table to operate. func (session *Session) Table(tableNameOrBean interface{}) *Session { - session.statement.Table(tableNameOrBean) + if err := session.statement.SetTable(tableNameOrBean); err != nil { + session.engine.logger.Error(err) + } return session } @@ -182,7 +190,7 @@ func (session *Session) ForUpdate() *Session { // NoAutoCondition disable generate SQL condition from beans func (session *Session) NoAutoCondition(no ...bool) *Session { - session.statement.NoAutoCondition(no...) + session.statement.SetNoAutoCondition(no...) return session } @@ -288,7 +296,7 @@ func (session *Session) canCache() bool { !session.statement.UseCache || session.statement.IsForUpdate || session.tx != nil || - len(session.statement.selectStr) > 0 { + len(session.statement.SelectStr) > 0 { return false } return true @@ -505,13 +513,13 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b continue } if fieldValue.CanAddr() { - err := DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) if err != nil { return nil, err } } else { x := reflect.New(fieldType) - err := DefaultJSONHandler.Unmarshal(bs, x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) if err != nil { return nil, err } @@ -535,13 +543,13 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b hasAssigned = true if len(bs) > 0 { if fieldValue.CanAddr() { - err := DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) if err != nil { return nil, err } } else { x := reflect.New(fieldType) - err := DefaultJSONHandler.Unmarshal(bs, x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) if err != nil { return nil, err } @@ -557,7 +565,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b hasAssigned = true if col.SQLType.IsText() { x := reflect.New(fieldType) - err := DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) if err != nil { return nil, err } @@ -672,7 +680,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b hasAssigned = true x := reflect.New(fieldType) if len([]byte(vv.String())) > 0 { - err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) if err != nil { return nil, err } @@ -682,7 +690,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b hasAssigned = true x := reflect.New(fieldType) if len(vv.Bytes()) > 0 { - err := DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) if err != nil { return nil, err } @@ -818,7 +826,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b case schemas.Complex64Type: var x complex64 if len([]byte(vv.String())) > 0 { - err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) if err != nil { return nil, err } @@ -828,7 +836,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b case schemas.Complex128Type: var x complex128 if len([]byte(vv.String())) > 0 { - err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) if err != nil { return nil, err } @@ -877,7 +885,7 @@ func (session *Session) LastSQL() (string, []interface{}) { // Unscoped always disable struct tag "deleted" func (session *Session) Unscoped() *Session { - session.statement.Unscoped() + session.statement.SetUnscoped() return session } diff --git a/session_cols.go b/session_cols.go index 4f7dc6cf..ca3589ab 100644 --- a/session_cols.go +++ b/session_cols.go @@ -63,19 +63,6 @@ func getFlagForColumn(m map[string]bool, col *schemas.Column) (val bool, has boo return false, false } -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 -} - // Incr provides a query string like "count = count + 1" func (session *Session) Incr(column string, arg ...interface{}) *Session { session.statement.Incr(column, arg...) diff --git a/session_cond.go b/session_cond.go index 72e3abc3..25d17148 100644 --- a/session_cond.go +++ b/session_cond.go @@ -51,5 +51,5 @@ func (session *Session) NotIn(column string, args ...interface{}) *Session { // Conds returns session query conditions except auto bean conditions func (session *Session) Conds() builder.Cond { - return session.statement.cond + return session.statement.Conds() } diff --git a/session_convert.go b/session_convert.go index e7eabecc..735aefa6 100644 --- a/session_convert.go +++ b/session_convert.go @@ -15,6 +15,7 @@ import ( "time" "xorm.io/xorm/convert" + "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -108,7 +109,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val case reflect.Complex64, reflect.Complex128: x := reflect.New(fieldType) if len(data) > 0 { - err := DefaultJSONHandler.Unmarshal(data, x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) if err != nil { session.engine.logger.Error(err) return err @@ -122,7 +123,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val if col.SQLType.IsText() { x := reflect.New(fieldType) if len(data) > 0 { - err := DefaultJSONHandler.Unmarshal(data, x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) if err != nil { session.engine.logger.Error(err) return err @@ -135,7 +136,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val } else { x := reflect.New(fieldType) if len(data) > 0 { - err := DefaultJSONHandler.Unmarshal(data, x.Interface()) + err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) if err != nil { session.engine.logger.Error(err) return err @@ -264,7 +265,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val case schemas.Complex64Type.Kind(): var x complex64 if len(data) > 0 { - err := DefaultJSONHandler.Unmarshal(data, &x) + err := json.DefaultJSONHandler.Unmarshal(data, &x) if err != nil { session.engine.logger.Error(err) return err @@ -275,7 +276,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val case schemas.Complex128Type.Kind(): var x complex128 if len(data) > 0 { - err := DefaultJSONHandler.Unmarshal(data, &x) + err := json.DefaultJSONHandler.Unmarshal(data, &x) if err != nil { session.engine.logger.Error(err) return err @@ -615,14 +616,14 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. } if col.SQLType.IsText() { - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { session.engine.logger.Error(err) return 0, err } return string(bytes), nil } else if col.SQLType.IsBlob() { - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { session.engine.logger.Error(err) return 0, err @@ -631,7 +632,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. } return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type()) case reflect.Complex64, reflect.Complex128: - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { session.engine.logger.Error(err) return 0, err @@ -643,7 +644,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. } if col.SQLType.IsText() { - bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { session.engine.logger.Error(err) return 0, err @@ -656,7 +657,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. (fieldValue.Type().Elem().Kind() == reflect.Uint8) { bytes = fieldValue.Bytes() } else { - bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) + bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { session.engine.logger.Error(err) return 0, err diff --git a/session_delete.go b/session_delete.go index 6bcb3852..f21151e1 100644 --- a/session_delete.go +++ b/session_delete.go @@ -23,7 +23,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri sqlStr = filter.Do(sqlStr) } - newsql := session.statement.convertIDSQL(sqlStr) + newsql := session.statement.ConvertIDSQL(sqlStr) if newsql == "" { return ErrCacheFailed } @@ -80,11 +80,11 @@ func (session *Session) Delete(bean interface{}) (int64, error) { defer session.Close() } - if session.statement.lastError != nil { - return 0, session.statement.lastError + if session.statement.LastError != nil { + return 0, session.statement.LastError } - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return 0, err } @@ -98,7 +98,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { processor.BeforeDelete() } - condSQL, condArgs, err := session.statement.genConds(bean) + condSQL, condArgs, err := session.statement.GenConds(bean) if err != nil { return 0, err } @@ -152,7 +152,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { var realSQL string argsForCache := make([]interface{}, 0, len(condArgs)*2) - if session.statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled + if session.statement.GetUnscoped() || table.DeletedColumn() == nil { // tag "deleted" is disabled realSQL = deleteSQL copy(argsForCache, condArgs) argsForCache = append(condArgs, argsForCache...) diff --git a/session_exist.go b/session_exist.go index d5b0c1d8..e52c618e 100644 --- a/session_exist.go +++ b/session_exist.go @@ -4,89 +4,19 @@ package xorm -import ( - "errors" - "fmt" - "reflect" - - "xorm.io/builder" - "xorm.io/xorm/schemas" -) - // Exist returns true if the record exist otherwise return false func (session *Session) Exist(bean ...interface{}) (bool, error) { if session.isAutoClose { defer session.Close() } - if session.statement.lastError != nil { - return false, session.statement.lastError + if session.statement.LastError != nil { + return false, session.statement.LastError } - var sqlStr string - var args []interface{} - var joinStr string - var err error - if session.statement.RawSQL == "" { - if len(bean) == 0 { - tableName := session.statement.TableName() - if len(tableName) <= 0 { - return false, ErrTableNotFound - } - - tableName = session.statement.quote(tableName) - if len(session.statement.JoinStr) > 0 { - joinStr = session.statement.JoinStr - } - - if session.statement.cond.IsValid() { - condSQL, condArgs, err := builder.ToSQL(session.statement.cond) - if err != nil { - return false, err - } - - if session.engine.dialect.DBType() == schemas.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) - } else if session.engine.dialect.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) - } - args = condArgs - } else { - if session.engine.dialect.DBType() == schemas.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) - } else if session.engine.dialect.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) - } - args = []interface{}{} - } - } else { - beanValue := reflect.ValueOf(bean[0]) - if beanValue.Kind() != reflect.Ptr { - return false, errors.New("needs a pointer") - } - - if beanValue.Elem().Kind() == reflect.Struct { - if err := session.statement.setRefBean(bean[0]); err != nil { - return false, err - } - } - - if len(session.statement.TableName()) <= 0 { - return false, ErrTableNotFound - } - session.statement.Limit(1) - sqlStr, args, err = session.statement.genGetSQL(bean[0]) - if err != nil { - return false, err - } - } - } else { - sqlStr = session.statement.RawSQL - args = session.statement.RawParams + sqlStr, args, err := session.statement.GenExistSQL(bean...) + if err != nil { + return false, err } rows, err := session.queryRows(sqlStr, args...) diff --git a/session_find.go b/session_find.go index 6903c1b9..a3ba2c82 100644 --- a/session_find.go +++ b/session_find.go @@ -8,10 +8,10 @@ import ( "errors" "fmt" "reflect" - "strings" "xorm.io/builder" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -53,8 +53,8 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte } session.autoResetStatement = true - if session.statement.selectStr != "" { - session.statement.selectStr = "" + if session.statement.SelectStr != "" { + session.statement.SelectStr = "" } if session.statement.OrderStr != "" { session.statement.OrderStr = "" @@ -66,8 +66,8 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error { defer session.resetStatement() - if session.statement.lastError != nil { - return session.statement.lastError + if session.statement.LastError != nil { + return session.statement.LastError } sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) @@ -82,7 +82,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Elem().Kind() == reflect.Struct { pv := reflect.New(sliceElementType.Elem()) - if err := session.statement.setRefValue(pv); err != nil { + if err := session.statement.SetRefValue(pv); err != nil { return err } } else { @@ -90,7 +90,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } } else if sliceElementType.Kind() == reflect.Struct { pv := reflect.New(sliceElementType) - if err := session.statement.setRefValue(pv); err != nil { + if err := session.statement.SetRefValue(pv); err != nil { return err } } else { @@ -103,16 +103,16 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) var addedTableName = (len(session.statement.JoinStr) > 0) var autoCond builder.Cond if tp == tpStruct { - if !session.statement.noAutoCondition && len(condiBean) > 0 { + if !session.statement.NoAutoCondition && len(condiBean) > 0 { var err error - autoCond, err = session.statement.buildConds(table, condiBean[0], true, true, false, true, addedTableName) + autoCond, err = session.statement.BuildConds(table, condiBean[0], true, true, false, true, addedTableName) if err != nil { return err } } else { // !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.unscoped { // tag "deleted" is enabled + 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() @@ -122,70 +122,20 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) colName = session.engine.Quote(nm) + "." + colName } - autoCond = session.engine.CondDeleted(col) + autoCond = session.statement.CondDeleted(col) } } } - var sqlStr string - var args []interface{} - var err error - if session.statement.RawSQL == "" { - if len(session.statement.TableName()) <= 0 { - return ErrTableNotFound - } - - var columnStr = session.statement.columnStr() - if len(session.statement.selectStr) > 0 { - columnStr = session.statement.selectStr - } else { - if session.statement.JoinStr == "" { - if columnStr == "" { - if session.statement.GroupByStr != "" { - columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) - } else { - columnStr = session.statement.genColumnStr() - } - } - } else { - if columnStr == "" { - if session.statement.GroupByStr != "" { - columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) - } else { - columnStr = "*" - } - } - } - if columnStr == "" { - columnStr = "*" - } - } - - session.statement.cond = session.statement.cond.And(autoCond) - condSQL, condArgs, err := builder.ToSQL(session.statement.cond) - if err != nil { - return err - } - - args = append(session.statement.joinArgs, condArgs...) - sqlStr, err = session.statement.genSelectSQL(columnStr, condSQL, true, true) - if err != nil { - return err - } - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - } else { - sqlStr = session.statement.RawSQL - args = session.statement.RawParams + sqlStr, args, err := session.statement.GenFindSQL(autoCond) + if err != nil { + return err } if session.canCache() { if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && !session.statement.IsDistinct && - !session.statement.unscoped { + !session.statement.GetUnscoped() { err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) if err != ErrCacheFailed { return err @@ -274,7 +224,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect if elemType.Kind() == reflect.Struct { var newValue = newElemFunc(fields) - dataStruct := rValue(newValue.Interface()) + dataStruct := utils.ReflectValue(newValue.Interface()) tb, err := session.engine.tagParser.MapType(dataStruct) if err != nil { return err @@ -323,8 +273,8 @@ func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) erro func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr interface{}, args ...interface{}) (err error) { if !session.canCache() || - indexNoCase(sqlStr, "having") != -1 || - indexNoCase(sqlStr, "group by") != -1 { + utils.IndexNoCase(sqlStr, "having") != -1 || + utils.IndexNoCase(sqlStr, "group by") != -1 { return ErrCacheFailed } @@ -338,7 +288,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sqlStr = filter.Do(sqlStr) } - newsql := session.statement.convertIDSQL(sqlStr) + newsql := session.statement.ConvertIDSQL(sqlStr) if newsql == "" { return ErrCacheFailed } diff --git a/session_find_test.go b/session_find_test.go index 8df3bc84..ad9d1668 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" ) @@ -299,13 +300,13 @@ func TestHaving(t *testing.T) { func TestOrderSameMapper(t *testing.T) { assert.NoError(t, prepareEngine()) - testEngine.UnMapType(rValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() testEngine.SetMapper(names.SameMapper{}) defer func() { - testEngine.UnMapType(rValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) testEngine.SetMapper(mapper) }() @@ -324,12 +325,12 @@ func TestOrderSameMapper(t *testing.T) { func TestHavingSameMapper(t *testing.T) { assert.NoError(t, prepareEngine()) - testEngine.UnMapType(rValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() testEngine.SetMapper(names.SameMapper{}) defer func() { - testEngine.UnMapType(rValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) testEngine.SetMapper(mapper) }() assertSync(t, new(Userinfo)) diff --git a/session_get.go b/session_get.go index d1e96958..f0fc016b 100644 --- a/session_get.go +++ b/session_get.go @@ -12,6 +12,7 @@ import ( "strconv" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -27,8 +28,8 @@ func (session *Session) Get(bean interface{}) (bool, error) { func (session *Session) get(bean interface{}) (bool, error) { defer session.resetStatement() - if session.statement.lastError != nil { - return false, session.statement.lastError + if session.statement.LastError != nil { + return false, session.statement.LastError } beanValue := reflect.ValueOf(bean) @@ -39,7 +40,7 @@ func (session *Session) get(bean interface{}) (bool, error) { } if beanValue.Elem().Kind() == reflect.Struct { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return false, err } } @@ -53,7 +54,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(bean) if err != nil { return false, err } @@ -66,7 +67,7 @@ func (session *Session) get(bean interface{}) (bool, error) { if session.canCache() && beanValue.Elem().Kind() == reflect.Struct { if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && - !session.statement.unscoped { + !session.statement.GetUnscoped() { has, err := session.cacheGet(bean, sqlStr, args...) if err != ErrCacheFailed { return has, err @@ -74,7 +75,7 @@ func (session *Session) get(bean interface{}) (bool, error) { } } - context := session.statement.context + context := session.statement.Context if context != nil { res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args)) if res != nil { @@ -244,7 +245,7 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, // close it before covert data rows.Close() - dataStruct := rValue(bean) + dataStruct := utils.ReflectValue(bean) _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table) if err != nil { return true, err @@ -274,7 +275,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf for _, filter := range session.engine.dialect.Filters() { sqlStr = filter.Do(sqlStr) } - newsql := session.statement.convertIDSQL(sqlStr) + newsql := session.statement.ConvertIDSQL(sqlStr) if newsql == "" { return false, ErrCacheFailed } diff --git a/session_get_test.go b/session_get_test.go index f1e8c7f6..b7eac2b4 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/contexts" "xorm.io/xorm/schemas" ) @@ -417,7 +418,7 @@ func TestContextGet(t *testing.T) { sess := testEngine.NewSession() defer sess.Close() - context := NewMemoryContextCache() + context := contexts.NewMemoryContextCache() var c2 ContextGetStruct has, err := sess.ID(1).NoCache().ContextCache(context).Get(&c2) @@ -452,7 +453,7 @@ func TestContextGet2(t *testing.T) { _, err := testEngine.Insert(&ContextGetStruct2{Name: "1"}) assert.NoError(t, err) - context := NewMemoryContextCache() + context := contexts.NewMemoryContextCache() var c2 ContextGetStruct2 has, err := testEngine.ID(1).NoCache().ContextCache(context).Get(&c2) diff --git a/session_insert.go b/session_insert.go index 4e822247..2206ad05 100644 --- a/session_insert.go +++ b/session_insert.go @@ -113,7 +113,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error return 0, errors.New("could not insert a empty slice") } - if err := session.statement.setRefBean(sliceValue.Index(0).Interface()); err != nil { + if err := session.statement.SetRefBean(sliceValue.Index(0).Interface()); err != nil { return 0, err } @@ -163,10 +163,10 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsDeleted { continue } - if session.statement.omitColumnMap.contain(col.Name) { + if session.statement.OmitColumnMap.Contain(col.Name) { continue } - if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) { + if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { continue } if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { @@ -178,7 +178,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error col := table.GetColumn(colName) setColumnTime(bean, col, t) }) - } else if col.IsVersion && session.statement.checkVersion { + } else if col.IsVersion && session.statement.CheckVersion { args = append(args, 1) var colName = col.Name session.afterClosures = append(session.afterClosures, func(bean interface{}) { @@ -214,10 +214,10 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsDeleted { continue } - if session.statement.omitColumnMap.contain(col.Name) { + if session.statement.OmitColumnMap.Contain(col.Name) { continue } - if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) { + if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { continue } if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { @@ -229,7 +229,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error col := table.GetColumn(colName) setColumnTime(bean, col, t) }) - } else if col.IsVersion && session.statement.checkVersion { + } else if col.IsVersion && session.statement.CheckVersion { args = append(args, 1) var colName = col.Name session.afterClosures = append(session.afterClosures, func(bean interface{}) { @@ -329,7 +329,7 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { } func (session *Session) innerInsert(bean interface{}) (int64, error) { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return 0, err } if len(session.statement.TableName()) <= 0 { @@ -353,7 +353,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 0, err } - exprs := session.statement.exprColumns + exprs := session.statement.ExprColumns colPlaces := strings.Repeat("?, ", len(colNames)) if exprs.Len() <= 0 && len(colPlaces) > 0 { colPlaces = colPlaces[0 : len(colPlaces)-2] @@ -385,25 +385,25 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 0, err } - if err := writeStrings(buf, append(colNames, exprs.colNames...), "`", "`"); err != nil { + if err := session.engine.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames...), ","); err != nil { return 0, err } - if session.statement.cond.IsValid() { + if session.statement.Conds().IsValid() { if _, err := buf.WriteString(fmt.Sprintf(")%s SELECT ", output)); err != nil { return 0, err } - if err := session.statement.writeArgs(buf, args); err != nil { + if err := session.statement.WriteArgs(buf, args); err != nil { return 0, err } - if len(exprs.args) > 0 { + if len(exprs.Args) > 0 { if _, err := buf.WriteString(","); err != nil { return 0, err } } - if err := exprs.writeArgs(buf); err != nil { + if err := exprs.WriteArgs(buf); err != nil { return 0, err } @@ -411,7 +411,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 0, err } - if err := session.statement.cond.WriteTo(buf); err != nil { + if err := session.statement.Conds().WriteTo(buf); err != nil { return 0, err } } else { @@ -423,7 +423,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 0, err } - if err := exprs.writeArgs(buf); err != nil { + if err := exprs.WriteArgs(buf); err != nil { return 0, err } @@ -482,7 +482,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { session.cacheInsert(tableName) - if table.Version != "" && session.statement.checkVersion { + if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) if err != nil { session.engine.logger.Error(err) @@ -523,7 +523,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { session.cacheInsert(tableName) - if table.Version != "" && session.statement.checkVersion { + if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) if err != nil { session.engine.logger.Error(err) @@ -564,7 +564,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { session.cacheInsert(tableName) - if table.Version != "" && session.statement.checkVersion { + if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) if err != nil { session.engine.logger.Error(err) @@ -637,19 +637,19 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac continue } - if session.statement.omitColumnMap.contain(col.Name) { + if session.statement.OmitColumnMap.Contain(col.Name) { continue } - if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) { + if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { continue } - if session.statement.incrColumns.isColExist(col.Name) { + if session.statement.IncrColumns.IsColExist(col.Name) { continue - } else if session.statement.decrColumns.isColExist(col.Name) { + } else if session.statement.DecrColumns.IsColExist(col.Name) { continue - } else if session.statement.exprColumns.isColExist(col.Name) { + } else if session.statement.ExprColumns.IsColExist(col.Name) { continue } @@ -681,7 +681,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac } // !evalphobia! set fieldValue as nil when column is nullable and zero-value - if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok { + if _, ok := getFlagForColumn(session.statement.NullableMap, col); ok { if col.Nullable && utils.IsValueZero(fieldValue) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) @@ -698,7 +698,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac col := table.GetColumn(colName) setColumnTime(bean, col, t) }) - } else if col.IsVersion && session.statement.checkVersion { + } else if col.IsVersion && session.statement.CheckVersion { args = append(args, 1) } else { arg, err := session.value2Interface(col, fieldValue) @@ -724,9 +724,9 @@ func (session *Session) insertMapInterface(m map[string]interface{}) (int64, err } var columns = make([]string, 0, len(m)) - exprs := session.statement.exprColumns + exprs := session.statement.ExprColumns for k := range m { - if !exprs.isColExist(k) { + if !exprs.IsColExist(k) { columns = append(columns, k) } } @@ -751,9 +751,9 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { } var columns = make([]string, 0, len(m)) - exprs := session.statement.exprColumns + exprs := session.statement.ExprColumns for k := range m { - if !exprs.isColExist(k) { + if !exprs.IsColExist(k) { columns = append(columns, k) } } @@ -774,15 +774,15 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, return 0, ErrTableNotFound } - exprs := session.statement.exprColumns + exprs := session.statement.ExprColumns w := builder.NewWriter() // if insert where - if session.statement.cond.IsValid() { + if session.statement.Conds().IsValid() { if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { return 0, err } - if err := writeStrings(w, append(columns, exprs.colNames...), "`", "`"); err != nil { + if err := session.engine.dialect.Quoter().JoinWrite(w.Builder, append(columns, exprs.ColNames...), ","); err != nil { return 0, err } @@ -790,15 +790,15 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, return 0, err } - if err := session.statement.writeArgs(w, args); err != nil { + if err := session.statement.WriteArgs(w, args); err != nil { return 0, err } - if len(exprs.args) > 0 { + if len(exprs.Args) > 0 { if _, err := w.WriteString(","); err != nil { return 0, err } - if err := exprs.writeArgs(w); err != nil { + if err := exprs.WriteArgs(w); err != nil { return 0, err } } @@ -807,7 +807,7 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, return 0, err } - if err := session.statement.cond.WriteTo(w); err != nil { + if err := session.statement.Conds().WriteTo(w); err != nil { return 0, err } } else { @@ -818,7 +818,7 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, return 0, err } - if err := writeStrings(w, append(columns, exprs.colNames...), "`", "`"); err != nil { + if err := session.engine.dialect.Quoter().JoinWrite(w.Builder, append(columns, exprs.ColNames...), ","); err != nil { return 0, err } if _, err := w.WriteString(fmt.Sprintf(") VALUES (%s", qm)); err != nil { @@ -826,11 +826,11 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, } w.Append(args...) - if len(exprs.args) > 0 { + if len(exprs.Args) > 0 { if _, err := w.WriteString(","); err != nil { return 0, err } - if err := exprs.writeArgs(w); err != nil { + if err := exprs.WriteArgs(w); err != nil { return 0, err } } diff --git a/session_iterate.go b/session_iterate.go index 4a3cc083..8cab8f48 100644 --- a/session_iterate.go +++ b/session_iterate.go @@ -6,6 +6,8 @@ package xorm import ( "reflect" + + "xorm.io/xorm/internal/utils" ) // IterFunc only use by Iterate @@ -25,11 +27,11 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { defer session.Close() } - if session.statement.lastError != nil { - return session.statement.lastError + if session.statement.LastError != nil { + return session.statement.LastError } - if session.statement.bufferSize > 0 { + if session.statement.BufferSize > 0 { return session.bufferIterate(bean, fun) } @@ -57,18 +59,18 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { // BufferSize sets the buffersize for iterate func (session *Session) BufferSize(size int) *Session { - session.statement.bufferSize = size + session.statement.BufferSize = size return session } func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error { - var bufferSize = session.statement.bufferSize + var bufferSize = session.statement.BufferSize var pLimitN = session.statement.LimitN if pLimitN != nil && bufferSize > *pLimitN { bufferSize = *pLimitN } var start = session.statement.Start - v := rValue(bean) + v := utils.ReflectValue(bean) sliceType := reflect.SliceOf(v.Type()) var idx = 0 session.autoResetStatement = false diff --git a/session_query.go b/session_query.go index 1783e154..12136466 100644 --- a/session_query.go +++ b/session_query.go @@ -8,83 +8,19 @@ import ( "fmt" "reflect" "strconv" - "strings" "time" - "xorm.io/builder" "xorm.io/xorm/core" "xorm.io/xorm/schemas" ) -func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { - if len(sqlOrArgs) > 0 { - return convertSQLOrArgs(sqlOrArgs...) - } - - if session.statement.RawSQL != "" { - return session.statement.RawSQL, session.statement.RawParams, nil - } - - if len(session.statement.TableName()) <= 0 { - return "", nil, ErrTableNotFound - } - - var columnStr = session.statement.columnStr() - if len(session.statement.selectStr) > 0 { - columnStr = session.statement.selectStr - } else { - if session.statement.JoinStr == "" { - if columnStr == "" { - if session.statement.GroupByStr != "" { - columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) - } else { - columnStr = session.statement.genColumnStr() - } - } - } else { - if columnStr == "" { - if session.statement.GroupByStr != "" { - columnStr = session.statement.quoteColumnStr(session.statement.GroupByStr) - } else { - columnStr = "*" - } - } - } - if columnStr == "" { - columnStr = "*" - } - } - - if err := session.statement.processIDParam(); err != nil { - return "", nil, err - } - - condSQL, condArgs, err := builder.ToSQL(session.statement.cond) - if err != nil { - return "", nil, err - } - - args := append(session.statement.joinArgs, condArgs...) - sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL, true, true) - if err != nil { - return "", nil, err - } - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - - return sqlStr, args, nil -} - // 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.genQuerySQL(sqlOrArgs...) + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) if err != nil { return nil, err } @@ -233,7 +169,7 @@ func (session *Session) QueryString(sqlOrArgs ...interface{}) ([]map[string]stri defer session.Close() } - sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) if err != nil { return nil, err } @@ -253,7 +189,7 @@ func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string, defer session.Close() } - sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) if err != nil { return nil, err } @@ -306,7 +242,7 @@ func (session *Session) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]i defer session.Close() } - sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) + sqlStr, args, err := session.statement.GenQuerySQL(sqlOrArgs...) if err != nil { return nil, err } diff --git a/session_raw.go b/session_raw.go index 51487779..efd74710 100644 --- a/session_raw.go +++ b/session_raw.go @@ -9,8 +9,8 @@ import ( "reflect" "time" - "xorm.io/builder" "xorm.io/xorm/core" + "xorm.io/xorm/internal/statements" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { @@ -196,20 +196,6 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er return session.DB().ExecContext(session.ctx, sqlStr, args...) } -func convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { - switch sqlOrArgs[0].(type) { - case string: - return sqlOrArgs[0].(string), sqlOrArgs[1:], nil - case *builder.Builder: - return sqlOrArgs[0].(*builder.Builder).ToSQL() - case builder.Builder: - bd := sqlOrArgs[0].(builder.Builder) - return bd.ToSQL() - } - - return "", nil, ErrUnSupportedType -} - // Exec raw sql func (session *Session) Exec(sqlOrArgs ...interface{}) (sql.Result, error) { if session.isAutoClose { @@ -220,7 +206,7 @@ func (session *Session) Exec(sqlOrArgs ...interface{}) (sql.Result, error) { return nil, ErrUnSupportedType } - sqlStr, args, err := convertSQLOrArgs(sqlOrArgs...) + sqlStr, args, err := statements.ConvertSQLOrArgs(sqlOrArgs...) if err != nil { return nil, err } diff --git a/session_schema.go b/session_schema.go index 05b24c91..0279ced7 100644 --- a/session_schema.go +++ b/session_schema.go @@ -33,11 +33,11 @@ func (session *Session) CreateTable(bean interface{}) error { } func (session *Session) createTable(bean interface{}) error { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } - sqlStr := session.statement.genCreateTableSQL() + sqlStr := session.statement.GenCreateTableSQL() _, err := session.exec(sqlStr) return err } @@ -52,11 +52,11 @@ func (session *Session) CreateIndexes(bean interface{}) error { } func (session *Session) createIndexes(bean interface{}) error { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } - sqls := session.statement.genIndexSQL() + sqls := session.statement.GenIndexSQL() for _, sqlStr := range sqls { _, err := session.exec(sqlStr) if err != nil { @@ -75,11 +75,11 @@ func (session *Session) CreateUniques(bean interface{}) error { } func (session *Session) createUniques(bean interface{}) error { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } - sqls := session.statement.genUniqueSQL() + sqls := session.statement.GenUniqueSQL() for _, sqlStr := range sqls { _, err := session.exec(sqlStr) if err != nil { @@ -99,11 +99,11 @@ func (session *Session) DropIndexes(bean interface{}) error { } func (session *Session) dropIndexes(bean interface{}) error { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return err } - sqls := session.statement.genDelIndexSQL() + sqls := session.statement.GenDelIndexSQL() for _, sqlStr := range sqls { _, err := session.exec(sqlStr) if err != nil { @@ -201,7 +201,7 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo func (session *Session) addColumn(colName string) error { col := session.statement.RefTable.GetColumn(colName) - sql := session.statement.dialect.AddColumnSQL(session.statement.TableName(), col) + sql := session.engine.dialect.AddColumnSQL(session.statement.TableName(), col) _, err := session.exec(sql) return err } @@ -241,7 +241,7 @@ func (session *Session) Sync2(beans ...interface{}) error { }() for _, bean := range beans { - v := rValue(bean) + v := utils.ReflectValue(bean) table, err := engine.tagParser.MapType(v) if err != nil { return err @@ -299,7 +299,7 @@ func (session *Session) Sync2(beans ...interface{}) error { // column is not exist on table if oriCol == nil { session.statement.RefTable = table - session.statement.tableName = tbNameWithSchema + session.statement.SetTableName(tbNameWithSchema) if err = session.addColumn(col.Name); err != nil { return err } @@ -409,11 +409,11 @@ func (session *Session) Sync2(beans ...interface{}) error { for name, index := range addedNames { if index.Type == schemas.UniqueType { session.statement.RefTable = table - session.statement.tableName = tbNameWithSchema + session.statement.SetTableName(tbNameWithSchema) err = session.addUnique(tbNameWithSchema, name) } else if index.Type == schemas.IndexType { session.statement.RefTable = table - session.statement.tableName = tbNameWithSchema + session.statement.SetTableName(tbNameWithSchema) err = session.addIndex(tbNameWithSchema, name) } if err != nil { diff --git a/session_stats.go b/session_stats.go index c2cac830..17d0a675 100644 --- a/session_stats.go +++ b/session_stats.go @@ -17,17 +17,9 @@ func (session *Session) Count(bean ...interface{}) (int64, error) { defer session.Close() } - var sqlStr string - var args []interface{} - var err error - if session.statement.RawSQL == "" { - sqlStr, args, err = session.statement.genCountSQL(bean...) - if err != nil { - return 0, err - } - } else { - sqlStr = session.statement.RawSQL - args = session.statement.RawParams + sqlStr, args, err := session.statement.GenCountSQL(bean...) + if err != nil { + return 0, err } var total int64 @@ -50,21 +42,12 @@ func (session *Session) sum(res interface{}, bean interface{}, columnNames ...st return errors.New("need a pointer to a variable") } - var isSlice = v.Elem().Kind() == reflect.Slice - var sqlStr string - var args []interface{} - var err error - if len(session.statement.RawSQL) == 0 { - sqlStr, args, err = session.statement.genSumSQL(bean, columnNames...) - if err != nil { - return err - } - } else { - sqlStr = session.statement.RawSQL - args = session.statement.RawParams + sqlStr, args, err := session.statement.GenSumSQL(bean, columnNames...) + if err != nil { + return err } - if isSlice { + if v.Elem().Kind() == reflect.Slice { err = session.queryRow(sqlStr, args...).ScanSlice(res) } else { err = session.queryRow(sqlStr, args...).Scan(res) diff --git a/session_tx_test.go b/session_tx_test.go index 1e3dcabf..303fd8d6 100644 --- a/session_tx_test.go +++ b/session_tx_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" ) @@ -85,10 +86,10 @@ func TestCombineTransactionSameMapper(t *testing.T) { assert.NoError(t, prepareEngine()) oldMapper := testEngine.GetColumnMapper() - testEngine.UnMapType(rValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) testEngine.SetMapper(names.SameMapper{}) defer func() { - testEngine.UnMapType(rValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) testEngine.SetMapper(oldMapper) }() diff --git a/session_update.go b/session_update.go index 4330afae..bb53c3a1 100644 --- a/session_update.go +++ b/session_update.go @@ -23,7 +23,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri return ErrCacheFailed } - oldhead, newsql := session.statement.convertUpdateSQL(sqlStr) + oldhead, newsql := session.statement.ConvertUpdateSQL(sqlStr) if newsql == "" { return ErrCacheFailed } @@ -88,12 +88,12 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri return err } if bean := cacher.GetBean(tableName, sid); bean != nil { - sqls := splitNNoCase(sqlStr, "where", 2) + sqls := utils.SplitNNoCase(sqlStr, "where", 2) if len(sqls) == 0 || len(sqls) > 2 { return ErrCacheFailed } - sqls = splitNNoCase(sqls[0], "set", 2) + sqls = utils.SplitNNoCase(sqls[0], "set", 2) if len(sqls) != 2 { return ErrCacheFailed } @@ -112,7 +112,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri session.engine.logger.Error(err) } else { session.engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) - if col.IsVersion && session.statement.checkVersion { + if col.IsVersion && session.statement.CheckVersion { session.incrVersionFieldValue(fieldValue) } else { fieldValue.Set(reflect.ValueOf(args[idx])) @@ -144,11 +144,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 defer session.Close() } - if session.statement.lastError != nil { - return 0, session.statement.lastError + if session.statement.LastError != nil { + return 0, session.statement.LastError } - v := rValue(bean) + v := utils.ReflectValue(bean) t := v.Type() var colNames []string @@ -168,7 +168,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var isMap = t.Kind() == reflect.Map var isStruct = t.Kind() == reflect.Struct if isStruct { - if err := session.statement.setRefBean(bean); err != nil { + if err := session.statement.SetRefBean(bean); err != nil { return 0, err } @@ -176,14 +176,14 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 return 0, ErrTableNotFound } - if session.statement.columnStr() == "" { - colNames, args = session.statement.buildUpdates(bean, false, false, + if session.statement.ColumnStr() == "" { + colNames, args, err = session.statement.BuildUpdates(bean, false, false, false, false, true) } else { colNames, args, err = session.genUpdateColumns(bean) - if err != nil { - return 0, err - } + } + if err != nil { + return 0, err } } else if isMap { colNames = make([]string, 0) @@ -201,8 +201,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 table := session.statement.RefTable if session.statement.UseAutoTime && table != nil && table.Updated != "" { - if !session.statement.columnMap.contain(table.Updated) && - !session.statement.omitColumnMap.contain(table.Updated) { + if !session.statement.ColumnMap.Contain(table.Updated) && + !session.statement.OmitColumnMap.Contain(table.Updated) { colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?") col := table.UpdatedColumn() val, t := session.engine.nowTime(col) @@ -219,21 +219,21 @@ 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 { + 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]) + args = append(args, incColumns.Args[i]) } // for update action to like "column = column - ?" - decColumns := session.statement.decrColumns - for i, colName := range decColumns.colNames { + 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]) + args = append(args, decColumns.Args[i]) } // for update action to like "column = expression" - exprColumns := session.statement.exprColumns - for i, colName := range exprColumns.colNames { - switch tp := exprColumns.args[i].(type) { + exprColumns := session.statement.ExprColumns + for i, colName := range exprColumns.ColNames { + switch tp := exprColumns.Args[i].(type) { case string: if len(tp) == 0 { tp = "''" @@ -248,16 +248,16 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 args = append(args, subArgs...) default: colNames = append(colNames, session.engine.Quote(colName)+"=?") - args = append(args, exprColumns.args[i]) + args = append(args, exprColumns.Args[i]) } } - if err = session.statement.processIDParam(); err != nil { + if err = session.statement.ProcessIDParam(); err != nil { return 0, err } var autoCond builder.Cond - if !session.statement.noAutoCondition { + if !session.statement.NoAutoCondition { condBeanIsStruct := false if len(condiBean) > 0 { if c, ok := condiBean[0].(map[string]interface{}); ok { @@ -270,7 +270,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } if k == reflect.Struct { var err error - autoCond, err = session.statement.buildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) + autoCond, err = session.statement.BuildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) if err != nil { return 0, err } @@ -282,8 +282,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } if !condBeanIsStruct && table != nil { - if col := table.DeletedColumn(); col != nil && !session.statement.unscoped { // tag "deleted" is enabled - autoCond1 := session.engine.CondDeleted(col) + if col := table.DeletedColumn(); col != nil && !session.statement.GetUnscoped() { // tag "deleted" is enabled + autoCond1 := session.statement.CondDeleted(col) if autoCond == nil { autoCond = autoCond1 @@ -294,15 +294,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - st := &session.statement + st := session.statement var ( sqlStr string condArgs []interface{} condSQL string - cond = session.statement.cond.And(autoCond) + cond = session.statement.Conds().And(autoCond) - doIncVer = isStruct && (table != nil && table.Version != "" && session.statement.checkVersion) + doIncVer = isStruct && (table != nil && table.Version != "" && session.statement.CheckVersion) verValue *reflect.Value ) if doIncVer { @@ -335,9 +335,9 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var top string if st.LimitN != nil { limitValue := *st.LimitN - if st.dialect.DBType() == schemas.MYSQL { + if session.engine.dialect.DBType() == schemas.MYSQL { condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) - } else if st.dialect.DBType() == schemas.SQLITE { + } else if session.engine.dialect.DBType() == schemas.SQLITE { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -348,7 +348,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.dialect.DBType() == schemas.POSTGRES { + } else if session.engine.dialect.DBType() == schemas.POSTGRES { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -360,8 +360,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.dialect.DBType() == schemas.MSSQL { - if st.OrderStr != "" && st.dialect.DBType() == schemas.MSSQL && + } else if session.engine.dialect.DBType() == schemas.MSSQL { + if st.OrderStr != "" && session.engine.dialect.DBType() == schemas.MSSQL && 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], @@ -459,7 +459,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac for _, col := range table.Columns() { if !col.IsVersion && !col.IsCreated && !col.IsUpdated { - if session.statement.omitColumnMap.contain(col.Name) { + if session.statement.OmitColumnMap.Contain(col.Name) { continue } } @@ -494,25 +494,25 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac } } - if (col.IsDeleted && !session.statement.unscoped) || col.IsCreated { + if (col.IsDeleted && !session.statement.GetUnscoped()) || col.IsCreated { continue } // if only update specify columns - if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) { + if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { continue } - if session.statement.incrColumns.isColExist(col.Name) { + if session.statement.IncrColumns.IsColExist(col.Name) { continue - } else if session.statement.decrColumns.isColExist(col.Name) { + } else if session.statement.DecrColumns.IsColExist(col.Name) { continue - } else if session.statement.exprColumns.isColExist(col.Name) { + } else if session.statement.ExprColumns.IsColExist(col.Name) { continue } // !evalphobia! set fieldValue as nil when column is nullable and zero-value - if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok { + if _, ok := getFlagForColumn(session.statement.NullableMap, col); ok { if col.Nullable && utils.IsValueZero(fieldValue) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) @@ -529,7 +529,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac col := table.GetColumn(colName) setColumnTime(bean, col, t) }) - } else if col.IsVersion && session.statement.checkVersion { + } else if col.IsVersion && session.statement.CheckVersion { args = append(args, 1) } else { arg, err := session.value2Interface(col, fieldValue) diff --git a/session_update_test.go b/session_update_test.go index 2d310aa1..0ef59155 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" ) @@ -685,20 +686,20 @@ func TestUpdateSameMapper(t *testing.T) { assert.NoError(t, prepareEngine()) oldMapper := testEngine.GetTableMapper() - testEngine.UnMapType(rValue(new(Userinfo)).Type()) - testEngine.UnMapType(rValue(new(Condi)).Type()) - testEngine.UnMapType(rValue(new(Article)).Type()) - testEngine.UnMapType(rValue(new(UpdateAllCols)).Type()) - testEngine.UnMapType(rValue(new(UpdateMustCols)).Type()) - testEngine.UnMapType(rValue(new(UpdateIncr)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Condi)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Article)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(UpdateAllCols)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(UpdateMustCols)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(UpdateIncr)).Type()) testEngine.SetMapper(names.SameMapper{}) defer func() { - testEngine.UnMapType(rValue(new(Userinfo)).Type()) - testEngine.UnMapType(rValue(new(Condi)).Type()) - testEngine.UnMapType(rValue(new(Article)).Type()) - testEngine.UnMapType(rValue(new(UpdateAllCols)).Type()) - testEngine.UnMapType(rValue(new(UpdateMustCols)).Type()) - testEngine.UnMapType(rValue(new(UpdateIncr)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Condi)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(Article)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(UpdateAllCols)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(UpdateMustCols)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(UpdateIncr)).Type()) testEngine.SetMapper(oldMapper) }() diff --git a/statement_test.go b/statement_test.go index 6e5564b0..57d6e477 100644 --- a/statement_test.go +++ b/statement_test.go @@ -5,185 +5,11 @@ package xorm import ( - "reflect" - "strings" "testing" "github.com/stretchr/testify/assert" - "xorm.io/xorm/schemas" ) -var colStrTests = []struct { - omitColumn string - onlyToDBColumnNdx int - expected string -}{ - {"", -1, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, - {"Code2", -1, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, - {"", 1, "`ID`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, - {"Code3", 1, "`ID`, `Caption`, `Code1`, `Code2`, `ParentID`, `Latitude`, `Longitude`"}, - {"Longitude", 1, "`ID`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`"}, - {"", 8, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`"}, -} - -func TestColumnsStringGeneration(t *testing.T) { - if dbType == "postgres" || dbType == "mssql" { - return - } - - var statement *Statement - - for ndx, testCase := range colStrTests { - statement = createTestStatement() - - if testCase.omitColumn != "" { - statement.Omit(testCase.omitColumn) - } - - columns := statement.RefTable.Columns() - if testCase.onlyToDBColumnNdx >= 0 { - columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB - } - - actual := statement.genColumnStr() - - if actual != testCase.expected { - t.Errorf("[test #%d] Unexpected columns string:\nwant:\t%s\nhave:\t%s", ndx, testCase.expected, actual) - } - if testCase.onlyToDBColumnNdx >= 0 { - columns[testCase.onlyToDBColumnNdx].MapType = schemas.TWOSIDES - } - } -} - -func BenchmarkColumnsStringGeneration(b *testing.B) { - b.StopTimer() - - statement := createTestStatement() - - testCase := colStrTests[0] - - if testCase.omitColumn != "" { - statement.Omit(testCase.omitColumn) // !nemec784! Column must be skipped - } - - if testCase.onlyToDBColumnNdx >= 0 { - columns := statement.RefTable.Columns() - columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB // !nemec784! Column must be skipped - } - - b.StartTimer() - - for i := 0; i < b.N; i++ { - actual := statement.genColumnStr() - - if actual != testCase.expected { - b.Errorf("Unexpected columns string:\nwant:\t%s\nhave:\t%s", testCase.expected, actual) - } - } -} - -func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { - - b.StopTimer() - - mapCols := make(map[string]bool) - cols := []*schemas.Column{ - {Name: `ID`}, - {Name: `IsDeleted`}, - {Name: `Caption`}, - {Name: `Code1`}, - {Name: `Code2`}, - {Name: `Code3`}, - {Name: `ParentID`}, - {Name: `Latitude`}, - {Name: `Longitude`}, - } - - for _, col := range cols { - mapCols[strings.ToLower(col.Name)] = true - } - - b.StartTimer() - - for i := 0; i < b.N; i++ { - - for _, col := range cols { - - if _, ok := getFlagForColumn(mapCols, col); !ok { - b.Fatal("Unexpected result") - } - } - } -} - -func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { - - b.StopTimer() - - mapCols := make(map[string]bool) - cols := []*schemas.Column{ - {Name: `ID`}, - {Name: `IsDeleted`}, - {Name: `Caption`}, - {Name: `Code1`}, - {Name: `Code2`}, - {Name: `Code3`}, - {Name: `ParentID`}, - {Name: `Latitude`}, - {Name: `Longitude`}, - } - - b.StartTimer() - - for i := 0; i < b.N; i++ { - - for _, col := range cols { - - if _, ok := getFlagForColumn(mapCols, col); ok { - b.Fatal("Unexpected result") - } - } - } -} - -type TestType struct { - ID int64 `xorm:"ID PK"` - IsDeleted bool `xorm:"IsDeleted"` - Caption string `xorm:"Caption"` - Code1 string `xorm:"Code1"` - Code2 string `xorm:"Code2"` - Code3 string `xorm:"Code3"` - ParentID int64 `xorm:"ParentID"` - Latitude float64 `xorm:"Latitude"` - Longitude float64 `xorm:"Longitude"` -} - -func (TestType) TableName() string { - return "TestTable" -} - -func createTestStatement() *Statement { - if engine, ok := testEngine.(*Engine); ok { - statement := &Statement{} - statement.Reset() - statement.Engine = engine - statement.dialect = engine.dialect - statement.setRefValue(reflect.ValueOf(TestType{})) - - return statement - } else if eg, ok := testEngine.(*EngineGroup); ok { - statement := &Statement{} - statement.Reset() - statement.Engine = eg.Engine - statement.dialect = eg.Engine.dialect - statement.setRefValue(reflect.ValueOf(TestType{})) - - return statement - } - return nil -} - func TestDistinctAndCols(t *testing.T) { type DistinctAndCols struct { Id int64 diff --git a/tags_test.go b/tags_test.go index b8a43670..9d41a5fa 100644 --- a/tags_test.go +++ b/tags_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" "xorm.io/xorm/schemas" ) @@ -608,10 +609,10 @@ func TestGonicMapperID(t *testing.T) { assert.NoError(t, prepareEngine()) oldMapper := testEngine.GetColumnMapper() - testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(IDGonicMapper)).Type()) testEngine.SetMapper(names.LintGonicMapper) defer func() { - testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(IDGonicMapper)).Type()) testEngine.SetMapper(oldMapper) }() @@ -645,10 +646,10 @@ func TestSameMapperID(t *testing.T) { assert.NoError(t, prepareEngine()) oldMapper := testEngine.GetColumnMapper() - testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(IDSameMapper)).Type()) testEngine.SetMapper(names.SameMapper{}) defer func() { - testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) + testEngine.UnMapType(utils.ReflectValue(new(IDSameMapper)).Type()) testEngine.SetMapper(oldMapper) }() @@ -818,7 +819,9 @@ func TestAutoIncrTag(t *testing.T) { Id int64 } - tb := testEngine.TableInfo(new(TestAutoIncr1)) + tb, err := testEngine.TableInfo(new(TestAutoIncr1)) + assert.NoError(t, err) + cols := tb.Columns() assert.EqualValues(t, 1, len(cols)) assert.True(t, cols[0].IsAutoIncrement) @@ -829,7 +832,9 @@ func TestAutoIncrTag(t *testing.T) { Id int64 `xorm:"id"` } - tb = testEngine.TableInfo(new(TestAutoIncr2)) + tb, err = testEngine.TableInfo(new(TestAutoIncr2)) + assert.NoError(t, err) + cols = tb.Columns() assert.EqualValues(t, 1, len(cols)) assert.False(t, cols[0].IsAutoIncrement) @@ -840,7 +845,9 @@ func TestAutoIncrTag(t *testing.T) { Id int64 `xorm:"'ID'"` } - tb = testEngine.TableInfo(new(TestAutoIncr3)) + tb, err = testEngine.TableInfo(new(TestAutoIncr3)) + assert.NoError(t, err) + cols = tb.Columns() assert.EqualValues(t, 1, len(cols)) assert.False(t, cols[0].IsAutoIncrement) @@ -851,7 +858,9 @@ func TestAutoIncrTag(t *testing.T) { Id int64 `xorm:"pk"` } - tb = testEngine.TableInfo(new(TestAutoIncr4)) + tb, err = testEngine.TableInfo(new(TestAutoIncr4)) + assert.NoError(t, err) + cols = tb.Columns() assert.EqualValues(t, 1, len(cols)) assert.False(t, cols[0].IsAutoIncrement) @@ -1035,7 +1044,9 @@ func TestTagDefault5(t *testing.T) { } assertSync(t, new(DefaultStruct5)) - table := testEngine.TableInfo(new(DefaultStruct5)) + table, err := testEngine.TableInfo(new(DefaultStruct5)) + assert.NoError(t, err) + createdCol := table.GetColumn("created") assert.NotNil(t, createdCol) assert.EqualValues(t, "'2006-01-02 15:04:05'", createdCol.Default) diff --git a/types_test.go b/types_test.go index 53872372..d8fd8309 100644 --- a/types_test.go +++ b/types_test.go @@ -10,6 +10,7 @@ import ( "testing" "xorm.io/xorm/convert" + "xorm.io/xorm/internal/json" "xorm.io/xorm/schemas" "github.com/stretchr/testify/assert" @@ -118,21 +119,21 @@ type ConvConfig struct { } func (s *ConvConfig) FromDB(data []byte) error { - return DefaultJSONHandler.Unmarshal(data, s) + return json.DefaultJSONHandler.Unmarshal(data, s) } func (s *ConvConfig) ToDB() ([]byte, error) { - return DefaultJSONHandler.Marshal(s) + return json.DefaultJSONHandler.Marshal(s) } type SliceType []*ConvConfig func (s *SliceType) FromDB(data []byte) error { - return DefaultJSONHandler.Unmarshal(data, s) + return json.DefaultJSONHandler.Unmarshal(data, s) } func (s *SliceType) ToDB() ([]byte, error) { - return DefaultJSONHandler.Marshal(s) + return json.DefaultJSONHandler.Marshal(s) } type ConvStruct struct { From 41388c2f56de7f3f74d56e218eaed30b0945313b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 29 Feb 2020 08:59:59 +0000 Subject: [PATCH 043/112] Use a new ContextLogger interface to implement logger (#1557) Fix bug Add log track on prepare & tx Some improvements remove unused codes refactor logger Fix bug log context add ContextLogger interface Reviewed-on: https://gitea.com/xorm/xorm/pulls/1557 --- core/db.go | 46 +++++++++++- core/stmt.go | 65 ++++++++++++++++- core/tx.go | 87 ++++++++++++++++++++--- dialects/dialect.go | 46 +++--------- dialects/mssql.go | 3 - dialects/mysql.go | 19 ++--- dialects/oracle.go | 4 -- dialects/postgres.go | 6 -- dialects/sqlite3.go | 12 +--- engine.go | 114 ++++++++++++------------------ engine_group.go | 10 +-- interface.go | 7 +- internal/statements/statement.go | 18 ++--- internal/statements/update.go | 2 +- log/logger_context.go | 108 ++++++++++++++++++++++++++++ schemas/table.go | 8 +-- schemas/type.go | 12 ++-- session.go | 25 ++----- session_convert.go | 18 ++--- session_delete.go | 4 +- session_find.go | 24 ++++--- session_get.go | 14 ++-- session_insert.go | 14 ++-- session_raw.go | 45 ++---------- session_schema.go | 2 +- session_tx.go | 116 ++++++++++++++++++++----------- session_update.go | 20 +++--- tags/parser.go | 79 +++++++++++++++------ tags/parser_test.go | 44 ++++++++++++ tags/tag.go | 2 +- tags_test.go | 2 +- xorm.go | 2 +- xorm_test.go | 2 +- 33 files changed, 617 insertions(+), 363 deletions(-) create mode 100644 log/logger_context.go create mode 100644 tags/parser_test.go diff --git a/core/db.go b/core/db.go index 8f16e848..592ccf18 100644 --- a/core/db.go +++ b/core/db.go @@ -12,7 +12,9 @@ import ( "reflect" "regexp" "sync" + "time" + "xorm.io/xorm/log" "xorm.io/xorm/names" ) @@ -81,6 +83,7 @@ type DB struct { Mapper names.Mapper reflectCache map[reflect.Type]*cacheStruct reflectCacheMutex sync.RWMutex + Logger log.SQLLogger } // Open opens a database @@ -120,7 +123,24 @@ func (db *DB) reflectNew(typ reflect.Type) reflect.Value { // QueryContext overwrites sql.DB.QueryContext func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + start := time.Now() + if db.Logger != nil { + db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + }) + } rows, err := db.DB.QueryContext(ctx, query, args...) + if db.Logger != nil { + db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { if rows != nil { rows.Close() @@ -209,7 +229,7 @@ func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) if err != nil { return nil, err } - return db.DB.ExecContext(ctx, query, args...) + return db.ExecContext(ctx, query, args...) } func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { @@ -221,7 +241,29 @@ func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{ if err != nil { return nil, err } - return db.DB.ExecContext(ctx, query, args...) + return db.ExecContext(ctx, query, args...) +} + +func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + start := time.Now() + if db.Logger != nil { + db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + }) + } + res, err := db.DB.ExecContext(ctx, query, args...) + if db.Logger != nil { + db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } + return res, err } func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { diff --git a/core/stmt.go b/core/stmt.go index 8a21541a..d3c46977 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -9,6 +9,9 @@ import ( "database/sql" "errors" "reflect" + "time" + + "xorm.io/xorm/log" ) // Stmt reprents a stmt objects @@ -16,6 +19,7 @@ type Stmt struct { *sql.Stmt db *DB names map[string]int + query string } func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { @@ -27,11 +31,27 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { return "?" }) + start := time.Now() + if db.Logger != nil { + db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: "PREPARE", + }) + } stmt, err := db.DB.PrepareContext(ctx, query) + if db.Logger != nil { + db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: "PREPARE", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { return nil, err } - return &Stmt{stmt, db, names}, nil + + return &Stmt{stmt, db, names, query}, nil } func (db *DB) Prepare(query string) (*Stmt, error) { @@ -48,7 +68,7 @@ func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, for k, i := range s.names { args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() } - return s.Stmt.ExecContext(ctx, args...) + return s.ExecContext(ctx, args...) } func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { @@ -65,15 +85,54 @@ func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Resul for k, i := range s.names { args[i] = vv.Elem().FieldByName(k).Interface() } - return s.Stmt.ExecContext(ctx, args...) + return s.ExecContext(ctx, args...) } func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { return s.ExecStructContext(context.Background(), st) } +func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { + start := time.Now() + if s.db.Logger != nil { + s.db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: s.query, + Args: args, + }) + } + res, err := s.Stmt.ExecContext(ctx, args) + if s.db.Logger != nil { + s.db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: s.query, + Args: args, + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } + return res, err +} + func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { + start := time.Now() + if s.db.Logger != nil { + s.db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: s.query, + Args: args, + }) + } rows, err := s.Stmt.QueryContext(ctx, args...) + if s.db.Logger != nil { + s.db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: s.query, + Args: args, + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { return nil, err } diff --git a/core/tx.go b/core/tx.go index a56b7006..10022efc 100644 --- a/core/tx.go +++ b/core/tx.go @@ -7,6 +7,9 @@ package core import ( "context" "database/sql" + "time" + + "xorm.io/xorm/log" ) type Tx struct { @@ -15,7 +18,22 @@ type Tx struct { } func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + start := time.Now() + if db.Logger != nil { + db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: "BEGIN TRANSACTION", + }) + } tx, err := db.DB.BeginTx(ctx, opts) + if db.Logger != nil { + db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: "BEGIN TRANSACTION", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { return nil, err } @@ -23,11 +41,7 @@ func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { } func (db *DB) Begin() (*Tx, error) { - tx, err := db.DB.Begin() - if err != nil { - return nil, err - } - return &Tx{tx, db}, nil + return db.BeginTx(context.Background(), nil) } func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { @@ -39,11 +53,26 @@ func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { return "?" }) + start := time.Now() + if tx.db.Logger != nil { + tx.db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: "PREPARE", + }) + } stmt, err := tx.Tx.PrepareContext(ctx, query) + if tx.db.Logger != nil { + tx.db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: "PREPARE", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { return nil, err } - return &Stmt{stmt, tx.db, names}, nil + return &Stmt{stmt, tx.db, names, query}, nil } func (tx *Tx) Prepare(query string) (*Stmt, error) { @@ -64,7 +93,7 @@ func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) if err != nil { return nil, err } - return tx.Tx.ExecContext(ctx, query, args...) + return tx.ExecContext(ctx, query, args...) } func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { @@ -76,7 +105,29 @@ func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{ if err != nil { return nil, err } - return tx.Tx.ExecContext(ctx, query, args...) + return tx.ExecContext(ctx, query, args...) +} + +func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + start := time.Now() + if tx.db.Logger != nil { + tx.db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + }) + } + res, err := tx.Tx.ExecContext(ctx, query, args...) + if tx.db.Logger != nil { + tx.db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } + return res, err } func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { @@ -84,8 +135,28 @@ func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { } func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + start := time.Now() + if tx.db.Logger != nil { + tx.db.Logger.BeforeSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + }) + } rows, err := tx.Tx.QueryContext(ctx, query, args...) + if tx.db.Logger != nil { + tx.db.Logger.AfterSQL(log.LogContext{ + Ctx: ctx, + SQL: query, + Args: args, + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { + if rows != nil { + rows.Close() + } return nil, err } return &Rows{rows, tx.db}, nil diff --git a/dialects/dialect.go b/dialects/dialect.go index e9e512ee..a0139d9f 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -11,14 +11,11 @@ import ( "time" "xorm.io/xorm/core" - "xorm.io/xorm/log" "xorm.io/xorm/schemas" ) -type DBType string - type URI struct { - DBType DBType + DBType schemas.DBType Proto string Host string Port string @@ -32,13 +29,12 @@ type URI struct { Schema string } -// a dialect is a driver's wrapper +// Dialect represents a kind of database type Dialect interface { - SetLogger(logger log.Logger) Init(*core.DB, *URI, string, string) error URI() *URI DB() *core.DB - DBType() DBType + DBType() schemas.DBType SQLType(*schemas.Column) string FormatBytes(b []byte) string DefaultSchema() string @@ -49,7 +45,6 @@ type Dialect interface { IsReserved(string) bool Quoter() schemas.Quoter - RollBackStr() string AutoIncrStr() string SupportInsertMany() bool @@ -92,7 +87,6 @@ type Base struct { dialect Dialect driverName string dataSourceName string - logger log.Logger uri *URI } @@ -100,10 +94,6 @@ func (b *Base) DB() *core.DB { return b.db } -func (b *Base) SetLogger(logger log.Logger) { - b.logger = logger -} - func (b *Base) DefaultSchema() string { return "" } @@ -118,7 +108,7 @@ func (b *Base) URI() *URI { return b.uri } -func (b *Base) DBType() DBType { +func (b *Base) DBType() schemas.DBType { return b.uri.DBType } @@ -187,10 +177,6 @@ func (b *Base) DataSourceName() string { return b.dataSourceName } -func (db *Base) RollBackStr() string { - return "ROLL BACK" -} - func (db *Base) SupportDropIfExists() bool { return true } @@ -201,7 +187,6 @@ func (db *Base) DropTableSQL(tableName string) string { } func (db *Base) HasRecords(ctx context.Context, query string, args ...interface{}) (bool, error) { - db.LogSQL(query, args) rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { return false, err @@ -229,13 +214,8 @@ func (db *Base) IsColumnExist(ctx context.Context, tableName, colName string) (b } func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { - quoter := db.dialect.Quoter() - sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), + return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), db.String(col)) - if db.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { - sql += " COMMENT '" + col.Comment + "'" - } - return sql } func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { @@ -323,16 +303,6 @@ func (b *Base) ForUpdateSQL(query string) string { return query + " FOR UPDATE" } -func (b *Base) LogSQL(sql string, args []interface{}) { - if b.logger != nil && b.logger.IsShowSQL() { - if len(args) > 0 { - b.logger.Infof("[SQL] %v %v", sql, args) - } else { - b.logger.Infof("[SQL] %v", sql) - } - } -} - func (b *Base) SetParams(params map[string]string) { } @@ -341,7 +311,7 @@ var ( ) // RegisterDialect register database dialect -func RegisterDialect(dbName DBType, dialectFunc func() Dialect) { +func RegisterDialect(dbName schemas.DBType, dialectFunc func() Dialect) { if dialectFunc == nil { panic("core: Register dialect is nil") } @@ -349,7 +319,7 @@ func RegisterDialect(dbName DBType, dialectFunc func() Dialect) { } // QueryDialect query if registered database dialect -func QueryDialect(dbName DBType) Dialect { +func QueryDialect(dbName schemas.DBType) Dialect { if d, ok := dialects[strings.ToLower(string(dbName))]; ok { return d() } @@ -358,7 +328,7 @@ func QueryDialect(dbName DBType) Dialect { func regDrvsNDialects() bool { providedDrvsNDialects := map[string]struct { - dbType DBType + dbType schemas.DBType getDriver func() Driver getDialect func() Dialect }{ diff --git a/dialects/mssql.go b/dialects/mssql.go index 83844f4e..9963fc4f 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -351,7 +351,6 @@ func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, ma LEFT OUTER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id where a.object_id=object_id('` + tableName + `')` - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -411,7 +410,6 @@ func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, ma func (db *mssql) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := `select name from sysobjects where xtype ='U'` - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -446,7 +444,6 @@ INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID AND IXCS.COLUMN_ID=C.COLUMN_ID WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? ` - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { diff --git a/dialects/mysql.go b/dialects/mysql.go index 62fc6eb1..5ed2d8f1 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -303,23 +303,26 @@ func (db *mysql) IndexCheckSQL(tableName, idxName string) (string, []interface{} return sql, args } -/*func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{db.DbName, tableName, colName} - sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" - return sql, args -}*/ - func (db *mysql) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{db.uri.DBName, tableName} sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" return sql, args } +func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { + quoter := db.dialect.Quoter() + sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), + db.String(col)) + if len(col.Comment) > 0 { + sql += " COMMENT '" + col.Comment + "'" + } + return sql +} + func (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{db.uri.DBName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -430,7 +433,6 @@ func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{db.uri.DBName} s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -459,7 +461,6 @@ func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { func (db *mysql) GetIndexes(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` = ?" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { diff --git a/dialects/oracle.go b/dialects/oracle.go index 1247d7a4..3b9989d9 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -635,7 +635,6 @@ func (db *oracle) IsColumnExist(ctx context.Context, tableName, colName string) args := []interface{}{tableName, colName} query := "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = :1" + " AND column_name = :2" - db.LogSQL(query, args) rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { @@ -653,7 +652,6 @@ func (db *oracle) GetColumns(ctx context.Context, tableName string) ([]string, m args := []interface{}{tableName} s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -750,7 +748,6 @@ func (db *oracle) GetColumns(ctx context.Context, tableName string) ([]string, m func (db *oracle) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT table_name FROM user_tables" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -775,7 +772,6 @@ func (db *oracle) GetIndexes(ctx context.Context, tableName string) (map[string] args := []interface{}{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 =:1" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { diff --git a/dialects/postgres.go b/dialects/postgres.go index 94514e95..2e314812 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -943,7 +943,6 @@ func (db *postgres) IsColumnExist(ctx context.Context, tableName, colName string query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + " AND column_name = $2" } - db.LogSQL(query, args) rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { @@ -975,8 +974,6 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att } s = fmt.Sprintf(s, f) - db.LogSQL(s, args) - rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err @@ -1077,8 +1074,6 @@ func (db *postgres) GetTables(ctx context.Context) ([]*schemas.Table, error) { s = s + " WHERE schemaname = $1" } - db.LogSQL(s, args) - rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, err @@ -1117,7 +1112,6 @@ func (db *postgres) GetIndexes(ctx context.Context, tableName string) (map[strin args = append(args, db.uri.Schema) s = s + " AND schemaname=$2" } - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 5511468f..7dfa7fca 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -249,16 +249,10 @@ func (db *sqlite3) ForUpdateSQL(query string) string { return query } -/*func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{tableName} - sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" - return sql, args -}*/ - func (db *sqlite3) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { args := []interface{}{tableName} query := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" - db.LogSQL(query, args) + rows, err := db.DB().QueryContext(ctx, query, args...) if err != nil { return false, err @@ -336,7 +330,7 @@ func parseString(colStr string) (*schemas.Column, error) { func (db *sqlite3) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" - db.LogSQL(s, args) + rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { return nil, nil, err @@ -393,7 +387,6 @@ func (db *sqlite3) GetColumns(ctx context.Context, tableName string) ([]string, func (db *sqlite3) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT name FROM sqlite_master WHERE type='table'" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { @@ -419,7 +412,6 @@ func (db *sqlite3) GetTables(ctx context.Context) ([]*schemas.Table, error) { func (db *sqlite3) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" - db.LogSQL(s, args) rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { diff --git a/engine.go b/engine.go index cf0126e9..b34f0716 100644 --- a/engine.go +++ b/engine.go @@ -31,22 +31,16 @@ import ( // Engine is the major struct of xorm, it means a database manager. // Commonly, an application only need one engine type Engine struct { - db *core.DB - dialect dialects.Dialect + cacherMgr *caches.Manager + db *core.DB + defaultContext context.Context + dialect dialects.Dialect + engineGroup *EngineGroup + logger log.ContextLogger + tagParser *tags.Parser - showSQL bool - showExecTime bool - - logger log.Logger TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database - - engineGroup *EngineGroup - - defaultContext context.Context - - tagParser *tags.Parser - cacherMgr *caches.Manager } func (engine *Engine) SetCacher(tableName string, cacher caches.Cacher) { @@ -67,32 +61,33 @@ func (engine *Engine) BufferSize(size int) *Session { // ShowSQL show SQL statement or not on logger if log level is great than INFO func (engine *Engine) ShowSQL(show ...bool) { engine.logger.ShowSQL(show...) - if len(show) == 0 { - engine.showSQL = true + if engine.logger.IsShowSQL() { + engine.db.Logger = engine.logger } else { - engine.showSQL = show[0] - } -} - -// ShowExecTime show SQL statement and execute time or not on logger if log level is great than INFO -func (engine *Engine) ShowExecTime(show ...bool) { - if len(show) == 0 { - engine.showExecTime = true - } else { - engine.showExecTime = show[0] + engine.db.Logger = &log.DiscardSQLLogger{} } } // Logger return the logger interface -func (engine *Engine) Logger() log.Logger { +func (engine *Engine) Logger() log.ContextLogger { return engine.logger } // SetLogger set the new logger -func (engine *Engine) SetLogger(logger log.Logger) { - engine.logger = logger - engine.showSQL = logger.IsShowSQL() - engine.dialect.SetLogger(logger) +func (engine *Engine) SetLogger(logger interface{}) { + var realLogger log.ContextLogger + switch t := logger.(type) { + case log.Logger: + realLogger = log.NewLoggerAdapter(t) + case log.ContextLogger: + realLogger = t + } + engine.logger = realLogger + if realLogger.IsShowSQL() { + engine.db.Logger = realLogger + } else { + engine.db.Logger = &log.DiscardSQLLogger{} + } } // SetLogLevel sets the logger level @@ -123,12 +118,12 @@ func (engine *Engine) SetMapper(mapper names.Mapper) { // SetTableMapper set the table name mapping rule func (engine *Engine) SetTableMapper(mapper names.Mapper) { - engine.tagParser.TableMapper = mapper + engine.tagParser.SetTableMapper(mapper) } // SetColumnMapper set the column name mapping rule func (engine *Engine) SetColumnMapper(mapper names.Mapper) { - engine.tagParser.ColumnMapper = mapper + engine.tagParser.SetColumnMapper(mapper) } // SupportInsertMany If engine's database support batch insert records like @@ -255,17 +250,6 @@ func (engine *Engine) Ping() error { return session.Ping() } -// logSQL save sql -func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) { - if engine.showSQL && !engine.showExecTime { - if len(sqlArgs) > 0 { - engine.logger.Infof("[SQL] %v %#v", sqlStr, sqlArgs) - } else { - engine.logger.Infof("[SQL] %v", sqlStr) - } - } -} - // SQL method let's you manually write raw SQL and operate // For example: // @@ -336,7 +320,7 @@ func (engine *Engine) DBMetas() ([]*schemas.Table, error) { } // DumpAllToFile dump database all table structs and data to a file -func (engine *Engine) DumpAllToFile(fp string, tp ...dialects.DBType) error { +func (engine *Engine) DumpAllToFile(fp string, tp ...schemas.DBType) error { f, err := os.Create(fp) if err != nil { return err @@ -346,7 +330,7 @@ func (engine *Engine) DumpAllToFile(fp string, tp ...dialects.DBType) error { } // DumpAll dump database all table structs and data to w -func (engine *Engine) DumpAll(w io.Writer, tp ...dialects.DBType) error { +func (engine *Engine) DumpAll(w io.Writer, tp ...schemas.DBType) error { tables, err := engine.DBMetas() if err != nil { return err @@ -355,7 +339,7 @@ func (engine *Engine) DumpAll(w io.Writer, tp ...dialects.DBType) error { } // DumpTablesToFile dump specified tables to SQL file. -func (engine *Engine) DumpTablesToFile(tables []*schemas.Table, fp string, tp ...dialects.DBType) error { +func (engine *Engine) DumpTablesToFile(tables []*schemas.Table, fp string, tp ...schemas.DBType) error { f, err := os.Create(fp) if err != nil { return err @@ -365,12 +349,12 @@ 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 ...dialects.DBType) error { +func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { return engine.dumpTables(tables, w, tp...) } // 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 ...dialects.DBType) error { +func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { var dialect dialects.Dialect var distDBName string if len(tp) == 0 { @@ -496,7 +480,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...dia } // FIXME: Hack for postgres - if string(dialect.DBType()) == schemas.POSTGRES && table.AutoIncrColumn() != nil { + if dialect.DBType() == schemas.POSTGRES && table.AutoIncrColumn() != nil { _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quoter().Quote(table.Name)+"), 1), false);\n") if err != nil { return err @@ -739,13 +723,9 @@ func (t *Table) IsValid() bool { } // TableInfo get table info according to bean's content -func (engine *Engine) TableInfo(bean interface{}) (*Table, error) { +func (engine *Engine) TableInfo(bean interface{}) (*schemas.Table, error) { v := utils.ReflectValue(bean) - tb, err := engine.tagParser.MapType(v) - if err != nil { - return nil, err - } - return &Table{tb, dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean)}, nil + return engine.tagParser.ParseWithCache(v) } // IsTableEmpty if a table has any reocrd @@ -763,7 +743,7 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { } // IDOf get id from one struct -func (engine *Engine) IDOf(bean interface{}) schemas.PK { +func (engine *Engine) IDOf(bean interface{}) (schemas.PK, error) { return engine.IDOfV(reflect.ValueOf(bean)) } @@ -773,18 +753,13 @@ func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string } // IDOfV get id from one value of struct -func (engine *Engine) IDOfV(rv reflect.Value) schemas.PK { - pk, err := engine.idOfV(rv) - if err != nil { - engine.logger.Error(err) - return nil - } - return pk +func (engine *Engine) IDOfV(rv reflect.Value) (schemas.PK, error) { + return engine.idOfV(rv) } func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) { v := reflect.Indirect(rv) - table, err := engine.tagParser.MapType(v) + table, err := engine.tagParser.ParseWithCache(v) if err != nil { return nil, err } @@ -882,7 +857,7 @@ func (engine *Engine) ClearCache(beans ...interface{}) error { // UnMapType remove table from tables cache func (engine *Engine) UnMapType(t reflect.Type) { - engine.tagParser.ClearTable(t) + engine.tagParser.ClearCacheTable(t) } // Sync the new struct changes to database, this method will automatically add @@ -895,7 +870,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { for _, bean := range beans { v := utils.ReflectValue(bean) tableNameNoSchema := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean) - table, err := engine.tagParser.MapType(v) + table, err := engine.tagParser.ParseWithCache(v) if err != nil { return err } @@ -1216,8 +1191,7 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { for scanner.Scan() { query := strings.Trim(scanner.Text(), " \t\n\r") if len(query) > 0 { - engine.logSQL(query) - result, err := engine.DB().Exec(query) + result, err := engine.DB().ExecContext(engine.defaultContext, query) results = append(results, result) if err != nil { return nil, err @@ -1244,12 +1218,12 @@ func (engine *Engine) formatColTime(col *schemas.Column, t time.Time) (v interfa // GetColumnMapper returns the column name mapper func (engine *Engine) GetColumnMapper() names.Mapper { - return engine.tagParser.ColumnMapper + return engine.tagParser.GetColumnMapper() } // GetTableMapper returns the table name mapper func (engine *Engine) GetTableMapper() names.Mapper { - return engine.tagParser.TableMapper + return engine.tagParser.GetTableMapper() } // GetTZLocation returns time zone of the application diff --git a/engine_group.go b/engine_group.go index 55159d55..8177697e 100644 --- a/engine_group.go +++ b/engine_group.go @@ -135,7 +135,7 @@ func (eg *EngineGroup) SetDefaultCacher(cacher caches.Cacher) { } // SetLogger set the new logger -func (eg *EngineGroup) SetLogger(logger log.Logger) { +func (eg *EngineGroup) SetLogger(logger interface{}) { eg.Engine.SetLogger(logger) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetLogger(logger) @@ -188,14 +188,6 @@ func (eg *EngineGroup) SetTableMapper(mapper names.Mapper) { } } -// ShowExecTime show SQL statement and execute time or not on logger if log level is great than INFO -func (eg *EngineGroup) ShowExecTime(show ...bool) { - eg.Engine.ShowExecTime(show...) - for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].ShowExecTime(show...) - } -} - // ShowSQL show SQL statement or not on logger if log level is great than INFO func (eg *EngineGroup) ShowSQL(show ...bool) { eg.Engine.ShowSQL(show...) diff --git a/interface.go b/interface.go index e7894012..13f1e12a 100644 --- a/interface.go +++ b/interface.go @@ -83,7 +83,7 @@ type EngineInterface interface { DBMetas() ([]*schemas.Table, error) Dialect() dialects.Dialect DropTables(...interface{}) error - DumpAllToFile(fp string, tp ...dialects.DBType) error + DumpAllToFile(fp string, tp ...schemas.DBType) error GetCacher(string) caches.Cacher GetColumnMapper() names.Mapper GetDefaultCacher() caches.Cacher @@ -98,7 +98,7 @@ type EngineInterface interface { SetConnMaxLifetime(time.Duration) SetColumnMapper(names.Mapper) SetDefaultCacher(caches.Cacher) - SetLogger(logger log.Logger) + SetLogger(logger interface{}) SetLogLevel(log.LogLevel) SetMapper(names.Mapper) SetMaxOpenConns(int) @@ -107,12 +107,11 @@ type EngineInterface interface { SetTableMapper(names.Mapper) SetTZDatabase(tz *time.Location) SetTZLocation(tz *time.Location) - ShowExecTime(...bool) ShowSQL(show ...bool) Sync(...interface{}) error Sync2(...interface{}) error StoreEngine(storeEngine string) *Session - TableInfo(bean interface{}) (*Table, error) + TableInfo(bean interface{}) (*schemas.Table, error) TableName(interface{}, ...bool) string UnMapType(reflect.Type) } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 92b1809a..68738b90 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -253,11 +253,11 @@ func (statement *Statement) NotIn(column string, args ...interface{}) *Statement func (statement *Statement) SetRefValue(v reflect.Value) error { var err error - statement.RefTable, err = statement.tagParser.MapType(reflect.Indirect(v)) + statement.RefTable, err = statement.tagParser.ParseWithCache(reflect.Indirect(v)) if err != nil { return err } - statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, v, true) + statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), v, true) return nil } @@ -267,11 +267,11 @@ func rValue(bean interface{}) reflect.Value { func (statement *Statement) SetRefBean(bean interface{}) error { var err error - statement.RefTable, err = statement.tagParser.MapType(rValue(bean)) + statement.RefTable, err = statement.tagParser.ParseWithCache(rValue(bean)) if err != nil { return err } - statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, bean, true) + statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), bean, true) return nil } @@ -507,13 +507,13 @@ func (statement *Statement) SetTable(tableNameOrBean interface{}) error { t := v.Type() if t.Kind() == reflect.Struct { var err error - statement.RefTable, err = statement.tagParser.MapType(v) + statement.RefTable, err = statement.tagParser.ParseWithCache(v) if err != nil { return err } } - statement.AltTableName = dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, tableNameOrBean, true) + statement.AltTableName = dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tableNameOrBean, true) return nil } @@ -554,7 +554,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition fmt.Fprintf(&buf, "(%s) %s ON %v", subSQL, aliasName, condition) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) default: - tbName := dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, tablename, true) + tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) if !utils.IsSubQuery(tbName) { var buf strings.Builder statement.dialect.Quoter().QuoteTo(&buf, tbName) @@ -689,7 +689,7 @@ func (statement *Statement) GenDelIndexSQL() []string { } else if index.Type == schemas.IndexType { rIdxName = utils.IndexName(idxPrefixName, idxName) } - sql := fmt.Sprintf("DROP INDEX %v", statement.quote(dialects.FullTableName(statement.dialect, statement.tagParser.TableMapper, rIdxName, true))) + sql := fmt.Sprintf("DROP INDEX %v", statement.quote(dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), rIdxName, true))) if statement.dialect.IndexOnTable() { sql += fmt.Sprintf(" ON %v", statement.quote(tbName)) } @@ -844,7 +844,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, val = bytes } } else { - table, err := statement.tagParser.MapType(fieldValue) + table, err := statement.tagParser.ParseWithCache(fieldValue) if err != nil { val = fieldValue.Interface() } else { diff --git a/internal/statements/update.go b/internal/statements/update.go index a5d7ec5a..e9cdd98c 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -187,7 +187,7 @@ func (statement *Statement) BuildUpdates(bean interface{}, val, _ = nulType.Value() } else { if !col.SQLType.IsJson() { - table, err := statement.tagParser.MapType(fieldValue) + table, err := statement.tagParser.ParseWithCache(fieldValue) if err != nil { val = fieldValue.Interface() } else { diff --git a/log/logger_context.go b/log/logger_context.go new file mode 100644 index 00000000..b05f1c52 --- /dev/null +++ b/log/logger_context.go @@ -0,0 +1,108 @@ +// Copyright 2020 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 log + +import ( + "context" + "time" +) + +// LogContext represents a log context +type LogContext struct { + Ctx context.Context + SQL string // log content or SQL + Args []interface{} // if it's a SQL, it's the arguments + ExecuteTime time.Duration + Err error // SQL executed error +} + +type SQLLogger interface { + BeforeSQL(context LogContext) + AfterSQL(context LogContext) +} + +type DiscardSQLLogger struct{} + +var _ SQLLogger = &DiscardSQLLogger{} + +func (DiscardSQLLogger) BeforeSQL(LogContext) {} +func (DiscardSQLLogger) AfterSQL(LogContext) {} + +// ContextLogger represents a logger interface with context +type ContextLogger interface { + SQLLogger + + Debugf(format string, v ...interface{}) + Errorf(format string, v ...interface{}) + Infof(format string, v ...interface{}) + Warnf(format string, v ...interface{}) + + Level() LogLevel + SetLevel(l LogLevel) + + ShowSQL(show ...bool) + IsShowSQL() bool +} + +var ( + _ ContextLogger = &LoggerAdapter{} +) + +// LoggerAdapter wraps a Logger interafce as LoggerContext interface +type LoggerAdapter struct { + logger Logger +} + +func NewLoggerAdapter(logger Logger) ContextLogger { + return &LoggerAdapter{ + logger: logger, + } +} + +func (l *LoggerAdapter) BeforeSQL(ctx LogContext) {} + +func (l *LoggerAdapter) AfterSQL(ctx LogContext) { + if !l.logger.IsShowSQL() { + return + } + + if ctx.ExecuteTime > 0 { + l.logger.Infof("[SQL] %v %v - %v", ctx.SQL, ctx.Args, ctx.ExecuteTime) + } else { + l.logger.Infof("[SQL] %v %v", ctx.SQL, ctx.Args) + } +} + +func (l *LoggerAdapter) Debugf(format string, v ...interface{}) { + l.logger.Debugf(format, v...) +} + +func (l *LoggerAdapter) Errorf(format string, v ...interface{}) { + l.logger.Errorf(format, v...) +} + +func (l *LoggerAdapter) Infof(format string, v ...interface{}) { + l.logger.Infof(format, v...) +} + +func (l *LoggerAdapter) Warnf(format string, v ...interface{}) { + l.logger.Warnf(format, v...) +} + +func (l *LoggerAdapter) Level() LogLevel { + return l.logger.Level() +} + +func (l *LoggerAdapter) SetLevel(lv LogLevel) { + l.logger.SetLevel(lv) +} + +func (l *LoggerAdapter) ShowSQL(show ...bool) { + l.logger.ShowSQL(show...) +} + +func (l *LoggerAdapter) IsShowSQL() bool { + return l.logger.IsShowSQL() +} diff --git a/schemas/table.go b/schemas/table.go index 44aa8152..2dac3ea2 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -7,7 +7,6 @@ package schemas import ( "reflect" "strings" - //"xorm.io/xorm/cache" ) // Table represents a database table @@ -24,10 +23,9 @@ type Table struct { Updated string Deleted string Version string - //Cacher caches.Cacher - StoreEngine string - Charset string - Comment string + StoreEngine string + Charset string + Comment string } func NewEmptyTable() *Table { diff --git a/schemas/type.go b/schemas/type.go index 2aaa2a44..39f1bf4e 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -11,12 +11,14 @@ import ( "time" ) +type DBType string + const ( - POSTGRES = "postgres" - SQLITE = "sqlite3" - MYSQL = "mysql" - MSSQL = "mssql" - ORACLE = "oracle" + POSTGRES DBType = "postgres" + SQLITE DBType = "sqlite3" + MYSQL DBType = "mysql" + MSSQL DBType = "mssql" + ORACLE DBType = "oracle" ) // SQLType represents SQL types diff --git a/session.go b/session.go index 92063882..287465ca 100644 --- a/session.go +++ b/session.go @@ -82,7 +82,7 @@ func (session *Session) Init() { session.engine.DatabaseTZ, ) - session.showSQL = session.engine.showSQL + //session.showSQL = session.engine.showSQL session.isAutoCommit = true session.isCommitedOrRollbacked = false session.isAutoClose = false @@ -165,7 +165,7 @@ func (session *Session) After(closures func(interface{})) *Session { // Table can input a string or pointer to struct for special a table to operate. func (session *Session) Table(tableNameOrBean interface{}) *Session { if err := session.statement.SetTable(tableNameOrBean); err != nil { - session.engine.logger.Error(err) + session.statement.LastError = err } return session } @@ -447,7 +447,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") { - session.engine.logger.Warn(err) + session.engine.logger.Warnf("%v", err) } continue } @@ -650,7 +650,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b hasAssigned = true t, err := session.byte2Time(col, d) if err != nil { - session.engine.logger.Error("byte2Time error:", err.Error()) + session.engine.logger.Errorf("byte2Time error: %v", err) hasAssigned = false } else { fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) @@ -659,7 +659,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b hasAssigned = true t, err := session.str2Time(col, d) if err != nil { - session.engine.logger.Error("byte2Time error:", err.Error()) + session.engine.logger.Errorf("byte2Time error: %v", err) hasAssigned = false } else { fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) @@ -672,7 +672,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b // !! 增加支持sql.Scanner接口的结构,如sql.NullString hasAssigned = true if err := nulVal.Scan(vv.Interface()); err != nil { - session.engine.logger.Error("sql.Sanner error:", err.Error()) + session.engine.logger.Errorf("sql.Sanner error: %v", err) hasAssigned = false } } else if col.SQLType.IsJson() { @@ -698,7 +698,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } } } else if session.statement.UseCascade { - table, err := session.engine.tagParser.MapType(*fieldValue) + table, err := session.engine.tagParser.ParseWithCache(*fieldValue) if err != nil { return nil, err } @@ -865,17 +865,6 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b func (session *Session) saveLastSQL(sql string, args ...interface{}) { session.lastSQL = sql session.lastSQLArgs = args - session.logSQL(sql, args...) -} - -func (session *Session) logSQL(sqlStr string, sqlArgs ...interface{}) { - if session.showSQL && !session.engine.showExecTime { - if len(sqlArgs) > 0 { - session.engine.logger.Infof("[SQL] %v %#v", sqlStr, sqlArgs) - } else { - session.engine.logger.Infof("[SQL] %v", sqlStr) - } - } } // LastSQL returns last query information diff --git a/session_convert.go b/session_convert.go index 735aefa6..1cd00627 100644 --- a/session_convert.go +++ b/session_convert.go @@ -111,7 +111,6 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val if len(data) > 0 { err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) if err != nil { - session.engine.logger.Error(err) return err } fieldValue.Set(x.Elem()) @@ -125,7 +124,6 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val if len(data) > 0 { err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) if err != nil { - session.engine.logger.Error(err) return err } fieldValue.Set(x.Elem()) @@ -138,7 +136,6 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val if len(data) > 0 { err := json.DefaultJSONHandler.Unmarshal(data, x.Interface()) if err != nil { - session.engine.logger.Error(err) return err } fieldValue.Set(x.Elem()) @@ -210,7 +207,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val v = x fieldValue.Set(reflect.ValueOf(v).Convert(fieldType)) } else if session.statement.UseCascade { - table, err := session.engine.tagParser.MapType(*fieldValue) + table, err := session.engine.tagParser.ParseWithCache(*fieldValue) if err != nil { return err } @@ -267,7 +264,6 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val if len(data) > 0 { err := json.DefaultJSONHandler.Unmarshal(data, &x) if err != nil { - session.engine.logger.Error(err) return err } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) @@ -278,7 +274,6 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val if len(data) > 0 { err := json.DefaultJSONHandler.Unmarshal(data, &x) if err != nil { - session.engine.logger.Error(err) return err } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) @@ -493,7 +488,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val default: if session.statement.UseCascade { structInter := reflect.New(fieldType.Elem()) - table, err := session.engine.tagParser.MapType(structInter.Elem()) + table, err := session.engine.tagParser.ParseWithCache(structInter.Elem()) if err != nil { return err } @@ -570,7 +565,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. if fieldValue.IsNil() { return nil, nil } else if !fieldValue.IsValid() { - session.engine.logger.Warn("the field[", col.FieldName, "] is invalid") + session.engine.logger.Warnf("the field [%s] is invalid", col.FieldName) return nil, nil } else { // !nashtsai! deference pointer type to instance type @@ -604,7 +599,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. return v.Value() } - fieldTable, err := session.engine.tagParser.MapType(fieldValue) + fieldTable, err := session.engine.tagParser.ParseWithCache(fieldValue) if err != nil { return nil, err } @@ -618,14 +613,12 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. if col.SQLType.IsText() { bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { - session.engine.logger.Error(err) return 0, err } return string(bytes), nil } else if col.SQLType.IsBlob() { bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { - session.engine.logger.Error(err) return 0, err } return bytes, nil @@ -634,7 +627,6 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. case reflect.Complex64, reflect.Complex128: bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { - session.engine.logger.Error(err) return 0, err } return string(bytes), nil @@ -646,7 +638,6 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. if col.SQLType.IsText() { bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { - session.engine.logger.Error(err) return 0, err } return string(bytes), nil @@ -659,7 +650,6 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. } else { bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { - session.engine.logger.Error(err) return 0, err } } diff --git a/session_delete.go b/session_delete.go index f21151e1..04200035 100644 --- a/session_delete.go +++ b/session_delete.go @@ -62,14 +62,14 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri } for _, id := range ids { - session.engine.logger.Debug("[cacheDelete] delete cache obj:", tableName, id) + session.engine.logger.Debugf("[cache] delete cache obj: %v, %v", tableName, id) sid, err := id.ToString() if err != nil { return err } cacher.DelBean(tableName, sid) } - session.engine.logger.Debug("[cacheDelete] clear cache table:", tableName) + session.engine.logger.Debugf("[cache] clear cache table: %v", tableName) cacher.ClearIds(tableName) return nil } diff --git a/session_find.go b/session_find.go index a3ba2c82..9551b767 100644 --- a/session_find.go +++ b/session_find.go @@ -141,7 +141,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) return err } err = nil // !nashtsai! reset err to nil for ErrCacheFailed - session.engine.logger.Warn("Cache Find Failed") + session.engine.logger.Warnf("Cache Find Failed") } } @@ -225,7 +225,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.MapType(dataStruct) + tb, err := session.engine.tagParser.ParseWithCache(dataStruct) if err != nil { return err } @@ -307,7 +307,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in for rows.Next() { i++ if i > 500 { - session.engine.logger.Debug("[cacheFind] ids length > 500, no cache") + session.engine.logger.Debugf("[cacheFind] ids length > 500, no cache") return ErrCacheFailed } var res = make([]string, len(table.PrimaryKeys)) @@ -326,13 +326,13 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in ids = append(ids, pk) } - session.engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, sqlStr, newsql, args) + 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) if err != nil { return err } } else { - session.engine.logger.Debug("[cacheFind] cache hit sql:", tableName, sqlStr, newsql, args) + session.engine.logger.Debugf("[cache] cache hit sql: %v, %v, %v, %v", tableName, sqlStr, newsql, args) } sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) @@ -365,16 +365,20 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in ides = append(ides, id) ididxes[sid] = idx } else { - session.engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean) + session.engine.logger.Debugf("[cache] cache hit bean: %v, %v, %v", tableName, id, bean) + + pk, err := session.engine.IDOf(bean) + if err != nil { + return err + } - pk := session.engine.IDOf(bean) xid, err := pk.ToString() if err != nil { return err } if sid != xid { - session.engine.logger.Error("[cacheFind] error cache", xid, sid, bean) + session.engine.logger.Errorf("[cache] error cache: %v, %v, %v", xid, sid, bean) return ErrCacheFailed } temps[idx] = bean @@ -424,7 +428,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in bean := rv.Interface() temps[ididxes[sid]] = bean - session.engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps) + session.engine.logger.Debugf("[cache] cache bean: %v, %v, %v, %v", tableName, id, bean, temps) cacher.PutBean(tableName, sid, bean) } } @@ -432,7 +436,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in for j := 0; j < len(temps); j++ { bean := temps[j] if bean == nil { - session.engine.logger.Warn("[cacheFind] cache no hit:", tableName, ids[j], temps) + session.engine.logger.Warnf("[cache] cache no hit: %v, %v, %v", tableName, ids[j], temps) // return errors.New("cache error") // !nashtsai! no need to return error, but continue instead continue } diff --git a/session_get.go b/session_get.go index f0fc016b..c468b440 100644 --- a/session_get.go +++ b/session_get.go @@ -79,7 +79,7 @@ func (session *Session) get(bean interface{}) (bool, error) { if context != nil { res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args)) if res != nil { - session.engine.logger.Debug("hit context cache", sqlStr) + session.engine.logger.Debugf("hit context cache: %s", sqlStr) structValue := reflect.Indirect(reflect.ValueOf(bean)) structValue.Set(reflect.Indirect(reflect.ValueOf(res))) @@ -283,7 +283,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf tableName := session.statement.TableName() cacher := session.engine.cacherMgr.GetCacher(tableName) - session.engine.logger.Debug("[cache] Get SQL:", newsql, args) + session.engine.logger.Debugf("[cache] Get SQL: %s, %v", newsql, args) table := session.statement.RefTable ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { @@ -319,19 +319,19 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } ids = []schemas.PK{pk} - session.engine.logger.Debug("[cache] cache ids:", newsql, ids) + session.engine.logger.Debugf("[cache] cache ids: %s, %v", newsql, ids) err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return false, err } } else { - session.engine.logger.Debug("[cache] cache hit:", newsql, ids) + session.engine.logger.Debugf("[cache] cache hit: %s, %v", newsql, ids) } if len(ids) > 0 { structValue := reflect.Indirect(reflect.ValueOf(bean)) id := ids[0] - session.engine.logger.Debug("[cache] get bean:", tableName, id) + session.engine.logger.Debugf("[cache] get bean: %s, %v", tableName, id) sid, err := id.ToString() if err != nil { return false, err @@ -344,10 +344,10 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return has, err } - session.engine.logger.Debug("[cache] cache bean:", tableName, id, cacheBean) + session.engine.logger.Debugf("[cache] cache bean: %s, %v, %v", tableName, id, cacheBean) cacher.PutBean(tableName, sid, cacheBean) } else { - session.engine.logger.Debug("[cache] cache hit:", tableName, id, cacheBean) + session.engine.logger.Debugf("[cache] cache hit: %s, %v, %v", tableName, id, cacheBean) has = true } structValue.Set(reflect.Indirect(reflect.ValueOf(cacheBean))) diff --git a/session_insert.go b/session_insert.go index 2206ad05..4662e25a 100644 --- a/session_insert.go +++ b/session_insert.go @@ -485,7 +485,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } else if verValue.IsValid() && verValue.CanSet() { session.incrVersionFieldValue(verValue) } @@ -503,7 +503,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue, err := table.AutoIncrColumn().ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { @@ -526,7 +526,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } else if verValue.IsValid() && verValue.CanSet() { session.incrVersionFieldValue(verValue) } @@ -544,7 +544,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue, err := table.AutoIncrColumn().ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { @@ -567,7 +567,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { if table.Version != "" && session.statement.CheckVersion { verValue, err := table.VersionColumn().ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } else if verValue.IsValid() && verValue.CanSet() { session.incrVersionFieldValue(verValue) } @@ -585,7 +585,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue, err := table.AutoIncrColumn().ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { @@ -617,7 +617,7 @@ func (session *Session) cacheInsert(table string) error { if cacher == nil { return nil } - session.engine.logger.Debug("[cache] clear sql:", table) + session.engine.logger.Debugf("[cache] clear sql: %v", table) cacher.ClearIds(table) return nil } diff --git a/session_raw.go b/session_raw.go index efd74710..02dcbf56 100644 --- a/session_raw.go +++ b/session_raw.go @@ -7,7 +7,6 @@ package xorm import ( "database/sql" "reflect" - "time" "xorm.io/xorm/core" "xorm.io/xorm/internal/statements" @@ -27,27 +26,8 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row session.queryPreprocess(&sqlStr, args...) - if session.showSQL { - session.lastSQL = sqlStr - session.lastSQLArgs = args - if session.engine.showExecTime { - b4ExecTime := time.Now() - defer func() { - execDuration := time.Since(b4ExecTime) - if len(args) > 0 { - session.engine.logger.Infof("[SQL] %s %#v - took: %v", sqlStr, args, execDuration) - } else { - session.engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration) - } - }() - } else { - if len(args) > 0 { - session.engine.logger.Infof("[SQL] %v %#v", sqlStr, args) - } else { - session.engine.logger.Infof("[SQL] %v", sqlStr) - } - } - } + session.lastSQL = sqlStr + session.lastSQLArgs = args if session.isAutoCommit { var db *core.DB @@ -156,25 +136,8 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er session.queryPreprocess(&sqlStr, args...) - if session.engine.showSQL { - if session.engine.showExecTime { - b4ExecTime := time.Now() - defer func() { - execDuration := time.Since(b4ExecTime) - if len(args) > 0 { - session.engine.logger.Infof("[SQL] %s %#v - took: %v", sqlStr, args, execDuration) - } else { - session.engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration) - } - }() - } else { - if len(args) > 0 { - session.engine.logger.Infof("[SQL] %v %#v", sqlStr, args) - } else { - session.engine.logger.Infof("[SQL] %v", sqlStr) - } - } - } + session.lastSQL = sqlStr + session.lastSQLArgs = args if !session.isAutoCommit { return session.tx.ExecContext(session.ctx, sqlStr, args...) diff --git a/session_schema.go b/session_schema.go index 0279ced7..3617a6b8 100644 --- a/session_schema.go +++ b/session_schema.go @@ -242,7 +242,7 @@ func (session *Session) Sync2(beans ...interface{}) error { for _, bean := range beans { v := utils.ReflectValue(bean) - table, err := engine.tagParser.MapType(v) + table, err := engine.tagParser.ParseWithCache(v) if err != nil { return err } diff --git a/session_tx.go b/session_tx.go index ee3d473f..7a4861c6 100644 --- a/session_tx.go +++ b/session_tx.go @@ -4,6 +4,12 @@ package xorm +import ( + "time" + + "xorm.io/xorm/log" +) + // Begin a transaction func (session *Session) Begin() error { if session.isAutoCommit { @@ -14,6 +20,7 @@ func (session *Session) Begin() error { session.isAutoCommit = false session.isCommitedOrRollbacked = false session.tx = tx + session.saveLastSQL("BEGIN TRANSACTION") } return nil @@ -22,10 +29,23 @@ func (session *Session) Begin() error { // Rollback When using transaction, you can rollback if any error func (session *Session) Rollback() error { if !session.isAutoCommit && !session.isCommitedOrRollbacked { - session.saveLastSQL(session.engine.dialect.RollBackStr()) + session.saveLastSQL("ROLL BACK") session.isCommitedOrRollbacked = true session.isAutoCommit = true - return session.tx.Rollback() + + start := time.Now() + session.engine.logger.BeforeSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "ROLL BACK", + }) + err := session.tx.Rollback() + session.engine.logger.AfterSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "ROLL BACK", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + return err } return nil } @@ -36,48 +56,62 @@ func (session *Session) Commit() error { session.saveLastSQL("COMMIT") session.isCommitedOrRollbacked = true session.isAutoCommit = true - var err error - if err = session.tx.Commit(); err == nil { - // handle processors after tx committed - closureCallFunc := func(closuresPtr *[]func(interface{}), bean interface{}) { - if closuresPtr != nil { - for _, closure := range *closuresPtr { - closure(bean) - } - } - } - for bean, closuresPtr := range session.afterInsertBeans { - closureCallFunc(closuresPtr, bean) + start := time.Now() + session.engine.logger.BeforeSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "COMMIT", + }) + err := session.tx.Commit() + session.engine.logger.AfterSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "COMMIT", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) - if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { - processor.AfterInsert() - } - } - for bean, closuresPtr := range session.afterUpdateBeans { - closureCallFunc(closuresPtr, bean) - - if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { - processor.AfterUpdate() - } - } - for bean, closuresPtr := range session.afterDeleteBeans { - closureCallFunc(closuresPtr, bean) - - if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { - processor.AfterDelete() - } - } - cleanUpFunc := func(slices *map[interface{}]*[]func(interface{})) { - if len(*slices) > 0 { - *slices = make(map[interface{}]*[]func(interface{}), 0) - } - } - cleanUpFunc(&session.afterInsertBeans) - cleanUpFunc(&session.afterUpdateBeans) - cleanUpFunc(&session.afterDeleteBeans) + if err != nil { + return err } - return err + + // handle processors after tx committed + closureCallFunc := func(closuresPtr *[]func(interface{}), bean interface{}) { + if closuresPtr != nil { + for _, closure := range *closuresPtr { + closure(bean) + } + } + } + + for bean, closuresPtr := range session.afterInsertBeans { + closureCallFunc(closuresPtr, bean) + + if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { + processor.AfterInsert() + } + } + for bean, closuresPtr := range session.afterUpdateBeans { + closureCallFunc(closuresPtr, bean) + + if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { + processor.AfterUpdate() + } + } + for bean, closuresPtr := range session.afterDeleteBeans { + closureCallFunc(closuresPtr, bean) + + if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { + processor.AfterDelete() + } + } + cleanUpFunc := func(slices *map[interface{}]*[]func(interface{})) { + if len(*slices) > 0 { + *slices = make(map[interface{}]*[]func(interface{}), 0) + } + } + cleanUpFunc(&session.afterInsertBeans) + cleanUpFunc(&session.afterUpdateBeans) + cleanUpFunc(&session.afterDeleteBeans) } return nil } diff --git a/session_update.go b/session_update.go index bb53c3a1..551b8167 100644 --- a/session_update.go +++ b/session_update.go @@ -30,7 +30,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri for _, filter := range session.engine.dialect.Filters() { newsql = filter.Do(newsql) } - session.engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql) + session.engine.logger.Debugf("[cache] new sql: %v, %v", oldhead, newsql) var nStart int if len(args) > 0 { @@ -43,7 +43,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri } cacher := session.engine.GetCacher(tableName) - session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) + session.engine.logger.Debugf("[cache] get cache sql: %v, %v", newsql, args[nStart:]) ids, err := caches.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { rows, err := session.NoCache().queryRows(newsql, args[nStart:]...) @@ -76,7 +76,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri ids = append(ids, pk) } - session.engine.logger.Debug("[cacheUpdate] find updated id", ids) + session.engine.logger.Debugf("[cache] find updated id: %v", ids) } /*else { session.engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args) cacher.DelIds(tableName, genSqlKey(newsql, args)) @@ -109,9 +109,9 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri if col := table.GetColumn(colName); col != nil { fieldValue, err := col.ValueOf(bean) if err != nil { - session.engine.logger.Error(err) + session.engine.logger.Errorf("%v", err) } else { - session.engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) + session.engine.logger.Debugf("[cache] set bean field: %v, %v, %v", bean, colName, fieldValue.Interface()) if col.IsVersion && session.statement.CheckVersion { session.incrVersionFieldValue(fieldValue) } else { @@ -119,16 +119,16 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri } } } else { - session.engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's", + session.engine.logger.Errorf("[cache] ERROR: column %v is not table %v's", colName, table.Name) } } - session.engine.logger.Debug("[cacheUpdate] update cache", tableName, id, bean) + session.engine.logger.Debugf("[cache] update cache: %v, %v, %v", tableName, id, bean) cacher.PutBean(tableName, sid, bean) } } - session.engine.logger.Debug("[cacheUpdate] clear cached table sql:", tableName) + session.engine.logger.Debugf("[cache] clear cached table sql: %v", tableName) cacher.ClearIds(tableName) return nil } @@ -414,7 +414,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if cacher := session.engine.GetCacher(tableName); cacher != nil && session.statement.UseCache { // session.cacheUpdate(table, tableName, sqlStr, args...) - session.engine.logger.Debug("[cacheUpdate] clear table ", tableName) + session.engine.logger.Debugf("[cache] clear table: %v", tableName) cacher.ClearIds(tableName) cacher.ClearBeans(tableName) } @@ -425,7 +425,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 closure(bean) } if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { - session.engine.logger.Debug("[event]", tableName, " has after update processor") + session.engine.logger.Debugf("[event] %v has after update processor", tableName) processor.AfterUpdate() } } else { diff --git a/tags/parser.go b/tags/parser.go index 5c94c55b..236d2d46 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -20,11 +20,15 @@ import ( "xorm.io/xorm/schemas" ) +var ( + ErrUnsupportedType = errors.New("Unsupported type") +) + type Parser struct { identifier string dialect dialects.Dialect - ColumnMapper names.Mapper - TableMapper names.Mapper + columnMapper names.Mapper + tableMapper names.Mapper handlers map[string]Handler cacherMgr *caches.Manager tableCache sync.Map // map[reflect.Type]*schemas.Table @@ -34,33 +38,39 @@ func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnM return &Parser{ identifier: identifier, dialect: dialect, - TableMapper: tableMapper, - ColumnMapper: columnMapper, + tableMapper: tableMapper, + columnMapper: columnMapper, handlers: defaultTagHandlers, cacherMgr: cacherMgr, } } -func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) { - if index, ok := table.Indexes[indexName]; ok { - index.AddColumn(col.Name) - col.Indexes[index.Name] = indexType - } else { - index := schemas.NewIndex(indexName, indexType) - index.AddColumn(col.Name) - table.AddIndex(index) - col.Indexes[index.Name] = indexType - } +func (parser *Parser) GetTableMapper() names.Mapper { + return parser.tableMapper } -func (parser *Parser) MapType(v reflect.Value) (*schemas.Table, error) { +func (parser *Parser) SetTableMapper(mapper names.Mapper) { + parser.ClearCaches() + parser.tableMapper = mapper +} + +func (parser *Parser) GetColumnMapper() names.Mapper { + return parser.columnMapper +} + +func (parser *Parser) SetColumnMapper(mapper names.Mapper) { + parser.ClearCaches() + parser.columnMapper = mapper +} + +func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) { t := v.Type() tableI, ok := parser.tableCache.Load(t) if ok { return tableI.(*schemas.Table), nil } - table, err := parser.mapType(v) + table, err := parser.Parse(v) if err != nil { return nil, err } @@ -78,16 +88,41 @@ func (parser *Parser) MapType(v reflect.Value) (*schemas.Table, error) { return table, nil } -// ClearTable removes the database mapper of a type from the cache -func (parser *Parser) ClearTable(t reflect.Type) { +// ClearCacheTable removes the database mapper of a type from the cache +func (parser *Parser) ClearCacheTable(t reflect.Type) { parser.tableCache.Delete(t) } -func (parser *Parser) mapType(v reflect.Value) (*schemas.Table, error) { +// ClearCaches removes all the cached table information parsed by structs +func (parser *Parser) ClearCaches() { + parser.tableCache = sync.Map{} +} + +func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) { + if index, ok := table.Indexes[indexName]; ok { + index.AddColumn(col.Name) + col.Indexes[index.Name] = indexType + } else { + index := schemas.NewIndex(indexName, indexType) + index.AddColumn(col.Name) + table.AddIndex(index) + col.Indexes[index.Name] = indexType + } +} + +// Parse parses a struct as a table information +func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { t := v.Type() + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Struct { + return nil, ErrUnsupportedType + } + table := schemas.NewEmptyTable() table.Type = t - table.Name = names.GetTableName(parser.TableMapper, v) + table.Name = names.GetTableName(parser.tableMapper, v) var idFieldColName string var hasCacheTag, hasNoCacheTag bool @@ -204,7 +239,7 @@ func (parser *Parser) mapType(v reflect.Value) (*schemas.Table, error) { col.Length2 = col.SQLType.DefaultLength2 } if col.Name == "" { - col.Name = parser.ColumnMapper.Obj2Table(t.Field(i).Name) + col.Name = parser.columnMapper.Obj2Table(t.Field(i).Name) } if ctx.isUnique { @@ -229,7 +264,7 @@ func (parser *Parser) mapType(v reflect.Value) (*schemas.Table, error) { } else { sqlType = schemas.Type2SQLType(fieldType) } - col = schemas.NewColumn(parser.ColumnMapper.Obj2Table(t.Field(i).Name), + col = schemas.NewColumn(parser.columnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, sqlType.DefaultLength, sqlType.DefaultLength2, true) diff --git a/tags/parser_test.go b/tags/parser_test.go new file mode 100644 index 00000000..6065bf2e --- /dev/null +++ b/tags/parser_test.go @@ -0,0 +1,44 @@ +// Copyright 2020 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 tags + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "xorm.io/xorm/caches" + "xorm.io/xorm/dialects" + "xorm.io/xorm/names" +) + +type ParseTableName1 struct{} + +type ParseTableName2 struct{} + +func (p ParseTableName2) TableName() string { + return "p_parseTableName" +} + +func TestParseTableName(t *testing.T) { + parser := NewParser( + "xorm", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.SnakeMapper{}, + caches.NewManager(), + ) + table, err := parser.Parse(reflect.ValueOf(new(ParseTableName1))) + assert.NoError(t, err) + assert.EqualValues(t, "parse_table_name1", table.Name) + + table, err = parser.Parse(reflect.ValueOf(new(ParseTableName2))) + assert.NoError(t, err) + assert.EqualValues(t, "p_parseTableName", table.Name) + + table, err = parser.Parse(reflect.ValueOf(ParseTableName2{})) + assert.NoError(t, err) + assert.EqualValues(t, "p_parseTableName", table.Name) +} diff --git a/tags/tag.go b/tags/tag.go index a043ed77..ee3f1e82 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -280,7 +280,7 @@ func ExtendsTagHandler(ctx *Context) error { isPtr = true fallthrough case reflect.Struct: - parentTable, err := ctx.parser.mapType(fieldValue) + parentTable, err := ctx.parser.Parse(fieldValue) if err != nil { return err } diff --git a/tags_test.go b/tags_test.go index 9d41a5fa..775fcf60 100644 --- a/tags_test.go +++ b/tags_test.go @@ -871,7 +871,7 @@ func TestAutoIncrTag(t *testing.T) { func TestTagComment(t *testing.T) { assert.NoError(t, prepareEngine()) // FIXME: only support mysql - if testEngine.Dialect().DriverName() != schemas.MYSQL { + if testEngine.Dialect().DBType() != schemas.MYSQL { return } diff --git a/xorm.go b/xorm.go index 2946b7c9..724a37cb 100644 --- a/xorm.go +++ b/xorm.go @@ -80,7 +80,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { logger := log.NewSimpleLogger(os.Stdout) logger.SetLevel(log.LOG_INFO) - engine.SetLogger(logger) + engine.SetLogger(log.NewLoggerAdapter(logger)) runtime.SetFinalizer(engine, close) diff --git a/xorm_test.go b/xorm_test.go index 2a24edb3..59f6c1a9 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -47,7 +47,7 @@ func createEngine(dbType, connStr string) error { var err error if !*cluster { - switch strings.ToLower(dbType) { + switch schemas.DBType(strings.ToLower(dbType)) { case schemas.MSSQL: db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "master", -1)) if err != nil { From c13117f0e6a592ecf1adbe07f460a8c5400df165 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 29 Feb 2020 09:30:26 +0000 Subject: [PATCH 044/112] Fix outdate changelog (#1565) Fix outdate changelog Reviewed-on: https://gitea.com/xorm/xorm/pulls/1565 --- README.md | 22 +--------------------- README_CN.md | 26 +++----------------------- 2 files changed, 4 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 17a6ed37..de4ba2f8 100644 --- a/README.md +++ b/README.md @@ -440,27 +440,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l ## Changelog -* **v0.7.0** - * Some bugs fixed - -* **v0.6.6** - * Some bugs fixed - -* **v0.6.5** - * Postgres schema support - * vgo support - * Add FindAndCount - * Database special params support via NewEngineWithParams - * Some bugs fixed - -* **v0.6.4** - * Automatical Read/Write seperatelly - * Query/QueryString/QueryInterface and action with Where/And - * Get support non-struct variables - * BufferSize on Iterate - * fix some other bugs. - -[More changes ...](https://github.com/go-xorm/manual-en-US/tree/master/chapter-16) +You can find all the changelog [here](CHANGELOG.md) ## Cases diff --git a/README_CN.md b/README_CN.md index 644bdc0b..5aae4fcb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -435,14 +435,14 @@ res, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) # 案例 -* [Go语言中文网](http://studygolang.com/) - [github.com/studygolang/studygolang](https://github.com/studygolang/studygolang) - * [Gitea](http://gitea.io) - [github.com/go-gitea/gitea](http://github.com/go-gitea/gitea) * [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) * [grafana](https://grafana.com/) - [github.com/grafana/grafana](http://github.com/grafana/grafana) +* [Go语言中文网](http://studygolang.com/) - [github.com/studygolang/studygolang](https://github.com/studygolang/studygolang) + * [github.com/m3ng9i/qreader](https://github.com/m3ng9i/qreader) * [Wego](http://github.com/go-tango/wego) @@ -470,27 +470,7 @@ res, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) ## 更新日志 -* **v0.7.0** - * 修正部分Bug - -* **v0.6.6** - * 修正部分Bug - -* **v0.6.5** - * 通过 engine.SetSchema 来支持 schema,当前仅支持Postgres - * vgo 支持 - * 新增 `FindAndCount` 函数 - * 通过 `NewEngineWithParams` 支持数据库特别参数 - * 修正部分Bug - -* **v0.6.4** - * 自动读写分离支持 - * Query/QueryString/QueryInterface 支持与 Where/And 合用 - * `Get` 支持获取非结构体变量 - * `Iterate` 支持 `BufferSize` - * 修正部分Bug - -[更多更新日志...](https://github.com/go-xorm/manual-zh-CN/tree/master/chapter-16) +请访问 [CHANGELOG.md](CHANGELOG.md) 获得更新日志。 ## LICENSE From 4e59597a49bc0c7ac657c842fae30a51edf2535f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 1 Mar 2020 03:05:26 +0000 Subject: [PATCH 045/112] Fix break session sql enable feature (#1566) Fix break session sql enable feature Reviewed-on: https://gitea.com/xorm/xorm/pulls/1566 --- core/db.go | 25 +++++++++++++++++----- core/stmt.go | 15 +++++++------ core/tx.go | 20 ++++++++++------- engine.go | 12 ++--------- log/logger_context.go | 15 ++----------- session.go | 9 +++----- session_test.go | 11 ++++++++++ session_tx.go | 50 ++++++++++++++++++++++++++----------------- 8 files changed, 89 insertions(+), 68 deletions(-) diff --git a/core/db.go b/core/db.go index 592ccf18..9aa771ba 100644 --- a/core/db.go +++ b/core/db.go @@ -83,7 +83,7 @@ type DB struct { Mapper names.Mapper reflectCache map[reflect.Type]*cacheStruct reflectCacheMutex sync.RWMutex - Logger log.SQLLogger + Logger log.ContextLogger } // Open opens a database @@ -108,6 +108,19 @@ func FromDB(db *sql.DB) *DB { } } +// NeedLogSQL returns true if need to log SQL +func (db *DB) NeedLogSQL(ctx context.Context) bool { + if db.Logger == nil { + return false + } + + v := ctx.Value("__xorm_show_sql") + if showSQL, ok := v.(bool); ok { + return showSQL + } + return db.Logger.IsShowSQL() +} + func (db *DB) reflectNew(typ reflect.Type) reflect.Value { db.reflectCacheMutex.Lock() defer db.reflectCacheMutex.Unlock() @@ -124,7 +137,8 @@ func (db *DB) reflectNew(typ reflect.Type) reflect.Value { // QueryContext overwrites sql.DB.QueryContext func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { start := time.Now() - if db.Logger != nil { + showSQL := db.NeedLogSQL(ctx) + if showSQL { db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: query, @@ -132,7 +146,7 @@ func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{ }) } rows, err := db.DB.QueryContext(ctx, query, args...) - if db.Logger != nil { + if showSQL { db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: query, @@ -246,7 +260,8 @@ func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{ func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { start := time.Now() - if db.Logger != nil { + showSQL := db.NeedLogSQL(ctx) + if showSQL { db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: query, @@ -254,7 +269,7 @@ func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{} }) } res, err := db.DB.ExecContext(ctx, query, args...) - if db.Logger != nil { + if showSQL { db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: query, diff --git a/core/stmt.go b/core/stmt.go index d3c46977..9d5954bd 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -32,14 +32,15 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { }) start := time.Now() - if db.Logger != nil { + showSQL := db.NeedLogSQL(ctx) + if showSQL { db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: "PREPARE", }) } stmt, err := db.DB.PrepareContext(ctx, query) - if db.Logger != nil { + if showSQL { db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: "PREPARE", @@ -94,7 +95,8 @@ func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { start := time.Now() - if s.db.Logger != nil { + showSQL := s.db.NeedLogSQL(ctx) + if showSQL { s.db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: s.query, @@ -102,7 +104,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result }) } res, err := s.Stmt.ExecContext(ctx, args) - if s.db.Logger != nil { + if showSQL { s.db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: s.query, @@ -116,7 +118,8 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { start := time.Now() - if s.db.Logger != nil { + showSQL := s.db.NeedLogSQL(ctx) + if showSQL { s.db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: s.query, @@ -124,7 +127,7 @@ func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, er }) } rows, err := s.Stmt.QueryContext(ctx, args...) - if s.db.Logger != nil { + if showSQL { s.db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: s.query, diff --git a/core/tx.go b/core/tx.go index 10022efc..07713267 100644 --- a/core/tx.go +++ b/core/tx.go @@ -19,14 +19,15 @@ type Tx struct { func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { start := time.Now() - if db.Logger != nil { + showSQL := db.NeedLogSQL(ctx) + if showSQL { db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: "BEGIN TRANSACTION", }) } tx, err := db.DB.BeginTx(ctx, opts) - if db.Logger != nil { + if showSQL { db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: "BEGIN TRANSACTION", @@ -54,14 +55,15 @@ func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { }) start := time.Now() - if tx.db.Logger != nil { + showSQL := tx.db.NeedLogSQL(ctx) + if showSQL { tx.db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: "PREPARE", }) } stmt, err := tx.Tx.PrepareContext(ctx, query) - if tx.db.Logger != nil { + if showSQL { tx.db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: "PREPARE", @@ -110,7 +112,8 @@ func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{ func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { start := time.Now() - if tx.db.Logger != nil { + showSQL := tx.db.NeedLogSQL(ctx) + if showSQL { tx.db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: query, @@ -118,7 +121,7 @@ func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{} }) } res, err := tx.Tx.ExecContext(ctx, query, args...) - if tx.db.Logger != nil { + if showSQL { tx.db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: query, @@ -136,7 +139,8 @@ func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { start := time.Now() - if tx.db.Logger != nil { + showSQL := tx.db.NeedLogSQL(ctx) + if showSQL { tx.db.Logger.BeforeSQL(log.LogContext{ Ctx: ctx, SQL: query, @@ -144,7 +148,7 @@ func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{ }) } rows, err := tx.Tx.QueryContext(ctx, query, args...) - if tx.db.Logger != nil { + if showSQL { tx.db.Logger.AfterSQL(log.LogContext{ Ctx: ctx, SQL: query, diff --git a/engine.go b/engine.go index b34f0716..421e89e0 100644 --- a/engine.go +++ b/engine.go @@ -61,11 +61,7 @@ func (engine *Engine) BufferSize(size int) *Session { // ShowSQL show SQL statement or not on logger if log level is great than INFO func (engine *Engine) ShowSQL(show ...bool) { engine.logger.ShowSQL(show...) - if engine.logger.IsShowSQL() { - engine.db.Logger = engine.logger - } else { - engine.db.Logger = &log.DiscardSQLLogger{} - } + engine.db.Logger = engine.logger } // Logger return the logger interface @@ -83,11 +79,7 @@ func (engine *Engine) SetLogger(logger interface{}) { realLogger = t } engine.logger = realLogger - if realLogger.IsShowSQL() { - engine.db.Logger = realLogger - } else { - engine.db.Logger = &log.DiscardSQLLogger{} - } + engine.db.Logger = realLogger } // SetLogLevel sets the logger level diff --git a/log/logger_context.go b/log/logger_context.go index b05f1c52..f80091f3 100644 --- a/log/logger_context.go +++ b/log/logger_context.go @@ -19,17 +19,10 @@ type LogContext struct { } type SQLLogger interface { - BeforeSQL(context LogContext) - AfterSQL(context LogContext) + BeforeSQL(context LogContext) // only invoked when IsShowSQL is true + AfterSQL(context LogContext) // only invoked when IsShowSQL is true } -type DiscardSQLLogger struct{} - -var _ SQLLogger = &DiscardSQLLogger{} - -func (DiscardSQLLogger) BeforeSQL(LogContext) {} -func (DiscardSQLLogger) AfterSQL(LogContext) {} - // ContextLogger represents a logger interface with context type ContextLogger interface { SQLLogger @@ -64,10 +57,6 @@ func NewLoggerAdapter(logger Logger) ContextLogger { func (l *LoggerAdapter) BeforeSQL(ctx LogContext) {} func (l *LoggerAdapter) AfterSQL(ctx LogContext) { - if !l.logger.IsShowSQL() { - return - } - if ctx.ExecuteTime > 0 { l.logger.Infof("[SQL] %v %v - %v", ctx.SQL, ctx.Args, ctx.ExecuteTime) } else { diff --git a/session.go b/session.go index 287465ca..4cab103f 100644 --- a/session.go +++ b/session.go @@ -58,8 +58,6 @@ type Session struct { prepareStmt bool stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) - // !evalphobia! stored the last executed query on this session - //beforeSQLExec func(string, ...interface{}) lastSQL string lastSQLArgs []interface{} showSQL bool @@ -82,7 +80,6 @@ func (session *Session) Init() { session.engine.DatabaseTZ, ) - //session.showSQL = session.engine.showSQL session.isAutoCommit = true session.isCommitedOrRollbacked = false session.isAutoClose = false @@ -241,11 +238,11 @@ func (session *Session) Cascade(trueOrFalse ...bool) *Session { // MustLogSQL means record SQL or not and don't follow engine's setting func (session *Session) MustLogSQL(log ...bool) *Session { + var showSQL = true if len(log) > 0 { - session.showSQL = log[0] - } else { - session.showSQL = true + showSQL = log[0] } + session.ctx = context.WithValue(session.ctx, "__xorm_show_sql", showSQL) return session } diff --git a/session_test.go b/session_test.go index 343f9baa..968842c3 100644 --- a/session_test.go +++ b/session_test.go @@ -43,3 +43,14 @@ func TestNullFloatStruct(t *testing.T) { }) assert.NoError(t, err) } + +func TestMustLogSQL(t *testing.T) { + assert.NoError(t, prepareEngine()) + testEngine.ShowSQL(false) + defer testEngine.ShowSQL(true) + + assertSync(t, new(Userinfo)) + + _, err := testEngine.Table("userinfo").MustLogSQL(true).Get(new(Userinfo)) + assert.NoError(t, err) +} diff --git a/session_tx.go b/session_tx.go index 7a4861c6..489489f3 100644 --- a/session_tx.go +++ b/session_tx.go @@ -34,17 +34,22 @@ func (session *Session) Rollback() error { session.isAutoCommit = true start := time.Now() - session.engine.logger.BeforeSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "ROLL BACK", - }) + needSQL := session.engine.db.NeedLogSQL(session.ctx) + if needSQL { + session.engine.logger.BeforeSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "ROLL BACK", + }) + } err := session.tx.Rollback() - session.engine.logger.AfterSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "ROLL BACK", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) + if needSQL { + session.engine.logger.AfterSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "ROLL BACK", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } return err } return nil @@ -58,17 +63,22 @@ func (session *Session) Commit() error { session.isAutoCommit = true start := time.Now() - session.engine.logger.BeforeSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "COMMIT", - }) + needSQL := session.engine.db.NeedLogSQL(session.ctx) + if needSQL { + session.engine.logger.BeforeSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "COMMIT", + }) + } err := session.tx.Commit() - session.engine.logger.AfterSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "COMMIT", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) + if needSQL { + session.engine.logger.AfterSQL(log.LogContext{ + Ctx: session.ctx, + SQL: "COMMIT", + ExecuteTime: time.Now().Sub(start), + Err: err, + }) + } if err != nil { return err From b079bc05ffc299c4e6d524f440c0685dd758a6eb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Mar 2020 08:19:32 +0000 Subject: [PATCH 046/112] Add cockroach support and tests (#896) Fix tests fix some tests and ignore some Add &experimental_serial_normalization=sql_sequence on test so that id will increase from 1 fix drone Fix drone Fix drone Fix drone Remove space Add makefile fix drone fix drone fix drone update fix tests use sql_sequence when testing fix postgres ci fix circle ci for cockroach upgrade cockroach version add trace on error add drone ci run cockroach background run cockroach backround run cockroach backround fix test fix run cockroach add cockroach test to circleci add cockroach support Add tests for table name (#1... Reviewed-on: https://gitea.com/xorm/xorm/pulls/896 --- .drone.yml | 33 +++++++++++++++++++++++++++++- Makefile | 18 +++++++++++++++++ circle.yml | 46 ++++++++++++++++++++++++++++++++++++++++++ dialects/postgres.go | 41 ++++++++++++++++++++++++++++++------- engine.go | 11 ++++++++-- session_get_test.go | 4 ++-- session_update_test.go | 5 +++++ tags_test.go | 1 + xorm_test.go | 17 +++++++++++----- 9 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 circle.yml diff --git a/.drone.yml b/.drone.yml index eb8ed320..dac49cdf 100644 --- a/.drone.yml +++ b/.drone.yml @@ -180,6 +180,25 @@ steps: - push - pull_request +- name: test-cockroach + pull: default + image: golang:1.13 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.cn" + TEST_COCKROACH_HOST: "cockroach:26257" + TEST_COCKROACH_DBNAME: xorm_test + TEST_COCKROACH_USERNAME: root + TEST_COCKROACH_PASSWORD: + commands: + - sleep 10 + - make test-cockroach + - TEST_CACHE_ENABLE=true make test-cockroach + when: + event: + - push + - pull_request + - name: merge_coverage pull: default image: golang:1.12 @@ -196,6 +215,7 @@ steps: - test-postgres-schema - test-mssql - test-tidb + - test-cockroach commands: - make coverage when: @@ -262,4 +282,15 @@ services: event: - push - tag - - pull_request \ No newline at end of file + - pull_request + +- name: cockroach + pull: default + image: cockroachdb/cockroach:v19.2.4 + commands: + - /cockroach/cockroach start --insecure + when: + event: + - push + - tag + - pull_request diff --git a/Makefile b/Makefile index 737ca96c..faad978f 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,12 @@ GOFILES := $(shell find . -name "*.go" -type f) PACKAGES ?= $(shell GO111MODULE=on $(GO) list ./...) +TEST_COCKROACH_HOST ?= cockroach:26257 +TEST_COCKROACH_SCHEMA ?= +TEST_COCKROACH_DBNAME ?= xorm_test +TEST_COCKROACH_USERNAME ?= postgres +TEST_COCKROACH_PASSWORD ?= + TEST_MSSQL_HOST ?= mssql:1433 TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_USERNAME ?= sa @@ -115,6 +121,18 @@ misspell-check: .PHONY: test test: test-sqlite +.PNONY: test-cockroach +test-cockroach: go-check + $(GO) test -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 + +.PHONY: test-cockroach\#% +test-cockroach\#%: go-check + $(GO) test -race -run $* -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 + .PNONY: test-mssql test-mssql: go-check $(GO) test -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) \ diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..8cacf5eb --- /dev/null +++ b/circle.yml @@ -0,0 +1,46 @@ +dependencies: + override: + # './...' is a relative pattern which means all subdirectories + - go get -t -d -v ./... + - go get -t -d -v github.com/go-xorm/tests + - go get -u github.com/go-xorm/core + - go get -u github.com/go-xorm/builder + - go build -v + +database: + override: + - mysql -u root -e "CREATE DATABASE xorm_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - mysql -u root -e "CREATE DATABASE xorm_test1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - mysql -u root -e "CREATE DATABASE xorm_test2 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - mysql -u root -e "CREATE DATABASE xorm_test3 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - createdb -p 5432 -e -U postgres xorm_test + - createdb -p 5432 -e -U postgres xorm_test1 + - createdb -p 5432 -e -U postgres xorm_test2 + - createdb -p 5432 -e -U postgres xorm_test3 + - psql xorm_test postgres -c "create schema xorm" + +test: + override: + # './...' is a relative pattern which means all subdirectories + - go get -u github.com/wadey/gocovmerge + - go test -v -race -db="sqlite3" -conn_str="./test.db" -coverprofile=coverage1-1.txt -covermode=atomic + - go test -v -race -db="sqlite3" -conn_str="./test.db" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic + - go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -coverprofile=coverage2-1.txt -covermode=atomic + - go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic + - go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -coverprofile=coverage3-1.txt -covermode=atomic + - go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic + - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -coverprofile=coverage4-1.txt -covermode=atomic + - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic + - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic + - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic + - wget -qO- https://binaries.cockroachdb.com/cockroach-v2.1.3.linux-amd64.tgz | tar xvz + - ./cockroach-v2.1.3.linux-amd64/cockroach start --insecure --background --host=localhost + - sleep 5 + - ./cockroach-v2.1.3.linux-amd64/cockroach sql --insecure --execute="create database xorm_test" + - go test -v -race -db="postgres" -conn_str="postgresql://root@localhost:26257/xorm_test?sslmode=disable" -coverprofile=coverage6-1.txt -covermode=atomic + - gocovmerge coverage1-1.txt coverage1-2.txt coverage2-1.txt coverage2-2.txt coverage3-1.txt coverage3-2.txt coverage4-1.txt coverage4-2.txt coverage5-1.txt coverage5-2.txt coverage6-1.txt> coverage.txt + - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./sqlite3.sh + - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./mysql.sh + - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./postgres.sh + post: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/dialects/postgres.go b/dialects/postgres.go index 2e314812..623b59ed 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -995,7 +995,6 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att return nil, nil, err } - // fmt.Println(args, colName, isNullable, dataType, maxLenStr, colDefault, isPK, isUnique) var maxLen int if maxLenStr != nil { maxLen, err = strconv.Atoi(*maxLenStr) @@ -1007,7 +1006,22 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att col.Name = strings.Trim(colName, `" `) if colDefault != nil { - col.Default = *colDefault + var theDefault = *colDefault + // cockroach has type with the default value with ::: + // and postgres with ::, we should remove them before store them + idx := strings.Index(theDefault, ":::") + if idx == -1 { + idx = strings.Index(theDefault, "::") + } + if idx > -1 { + theDefault = theDefault[:idx] + } + + if strings.HasSuffix(theDefault, "+00:00'") { + theDefault = theDefault[:len(theDefault)-7] + "'" + } + + col.Default = theDefault col.DefaultIsEmpty = false if strings.HasPrefix(col.Default, "nextval(") { col.IsAutoIncrement = true @@ -1022,8 +1036,8 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att col.Nullable = (isNullable == "YES") - switch dataType { - case "character varying", "character": + switch strings.ToLower(dataType) { + case "character varying", "character", "string": col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 0, DefaultLength2: 0} case "timestamp without time zone": col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0} @@ -1035,13 +1049,22 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att col.SQLType = schemas.SQLType{Name: schemas.Bool, DefaultLength: 0, DefaultLength2: 0} case "time without time zone": col.SQLType = schemas.SQLType{Name: schemas.Time, DefaultLength: 0, DefaultLength2: 0} + case "bytes": + col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: 0, DefaultLength2: 0} case "oid": col.SQLType = schemas.SQLType{Name: schemas.BigInt, DefaultLength: 0, DefaultLength2: 0} default: - col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} + startIdx := strings.Index(strings.ToLower(dataType), "string(") + if startIdx != -1 && strings.HasSuffix(dataType, ")") { + length := dataType[startIdx+8 : len(dataType)-1] + l, _ := strconv.Atoi(length) + col.SQLType = schemas.SQLType{Name: "STRING", DefaultLength: l, DefaultLength2: 0} + } else { + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} + } } if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, fmt.Errorf("Unknown colType: %v", dataType) + return nil, nil, fmt.Errorf("Unknown colType: %s - %s", dataType, col.SQLType.Name) } col.Length = maxLen @@ -1128,6 +1151,10 @@ func (db *postgres) GetIndexes(ctx context.Context, tableName string) (map[strin if err != nil { return nil, err } + + if indexName == "primary" { + continue + } indexName = strings.Trim(indexName, `" `) if strings.HasSuffix(indexName, "_pkey") { continue @@ -1149,7 +1176,7 @@ func (db *postgres) GetIndexes(ctx context.Context, tableName string) (map[strin index := &schemas.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} for _, colName := range colNames { - index.Cols = append(index.Cols, strings.Trim(colName, `" `)) + index.Cols = append(index.Cols, strings.TrimSpace(strings.Replace(colName, `"`, "", -1))) } index.IsRegular = isRegular indexes[index.Name] = index diff --git a/engine.go b/engine.go index 421e89e0..7865b7cb 100644 --- a/engine.go +++ b/engine.go @@ -284,12 +284,19 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { } table.Indexes = indexes + var seq int for _, index := range indexes { for _, name := range index.Cols { - if col := table.GetColumn(name); col != nil { + parts := strings.Split(name, " ") + if len(parts) > 1 { + if parts[1] == "DESC" { + seq = 1 + } + } + if col := table.GetColumn(parts[0]); col != nil { col.Indexes[index.Name] = index.Type } else { - return fmt.Errorf("Unknown col %s in index %v of table %v, columns %v", name, index.Name, table.Name, table.ColumnsSeq()) + return fmt.Errorf("Unknown col %s seq %d, in index %v of table %v, columns %v", name, seq, index.Name, table.Name, table.ColumnsSeq()) } } } diff --git a/session_get_test.go b/session_get_test.go index b7eac2b4..5bac9cd7 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -335,13 +335,13 @@ func TestJSONString(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 1, js.Id) - assert.EqualValues(t, `["1","2"]`, js.Content) + assert.True(t, `["1","2"]` == js.Content || `["1", "2"]` == js.Content) var jss []JsonString err = testEngine.Table("json_json").Find(&jss) assert.NoError(t, err) assert.EqualValues(t, 1, len(jss)) - assert.EqualValues(t, `["1","2"]`, jss[0].Content) + assert.True(t, `["1","2"]` == jss[0].Content || `["1", "2"]` == jss[0].Content) } func TestGetActionMapping(t *testing.T) { diff --git a/session_update_test.go b/session_update_test.go index 0ef59155..f76e447b 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -42,6 +42,11 @@ func TestUpdateMap(t *testing.T) { } func TestUpdateLimit(t *testing.T) { + if *ingoreUpdateLimit { + t.Skip() + return + } + assert.NoError(t, prepareEngine()) type UpdateTable2 struct { diff --git a/tags_test.go b/tags_test.go index 775fcf60..295affd8 100644 --- a/tags_test.go +++ b/tags_test.go @@ -1031,6 +1031,7 @@ func TestTagDefault4(t *testing.T) { } assert.True(t, isDefaultExist) assert.True(t, "CURRENT_TIMESTAMP" == defaultVal || + "current_timestamp()" == defaultVal || // for cockroach "now()" == defaultVal || "getdate" == defaultVal, defaultVal) } diff --git a/xorm_test.go b/xorm_test.go index 59f6c1a9..c1f38757 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -37,9 +37,9 @@ var ( splitter = flag.String("splitter", ";", "the splitter on connstr for cluster") schema = flag.String("schema", "", "specify the schema") ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb") - - tableMapper names.Mapper - colMapper names.Mapper + ingoreUpdateLimit = flag.Bool("ignore_update_limit", false, "ignore update limit if implementation difference, only for cockroach") + tableMapper names.Mapper + colMapper names.Mapper ) func createEngine(dbType, connStr string) error { @@ -59,11 +59,11 @@ func createEngine(dbType, connStr string) error { db.Close() *ignoreSelectUpdate = true case schemas.POSTGRES: - db, err := sql.Open(dbType, connStr) + db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "postgres", -1)) if err != nil { return err } - rows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = 'xorm_test'")) + rows, err := db.Query("SELECT 1 FROM pg_database WHERE datname = 'xorm_test'") if err != nil { return fmt.Errorf("db.Query: %v", err) } @@ -75,6 +75,12 @@ func createEngine(dbType, connStr string) error { } } if *schema != "" { + db.Close() + db, err = sql.Open(dbType, connStr) + if err != nil { + return err + } + defer db.Close() if _, err = db.Exec("CREATE SCHEMA IF NOT EXISTS " + *schema); err != nil { return fmt.Errorf("CREATE SCHEMA: %v", err) } @@ -178,6 +184,7 @@ func TestMain(m *testing.M) { if err := prepareEngine(); err != nil { fmt.Println(err) + os.Exit(1) return } From 36c62d15f35172372e0311276784804409c9cb14 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Mar 2020 09:13:46 +0000 Subject: [PATCH 047/112] Remove circle file (#1569) Remove circle file Reviewed-on: https://gitea.com/xorm/xorm/pulls/1569 --- circle.yml | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 8cacf5eb..00000000 --- a/circle.yml +++ /dev/null @@ -1,46 +0,0 @@ -dependencies: - override: - # './...' is a relative pattern which means all subdirectories - - go get -t -d -v ./... - - go get -t -d -v github.com/go-xorm/tests - - go get -u github.com/go-xorm/core - - go get -u github.com/go-xorm/builder - - go build -v - -database: - override: - - mysql -u root -e "CREATE DATABASE xorm_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" - - mysql -u root -e "CREATE DATABASE xorm_test1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" - - mysql -u root -e "CREATE DATABASE xorm_test2 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" - - mysql -u root -e "CREATE DATABASE xorm_test3 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" - - createdb -p 5432 -e -U postgres xorm_test - - createdb -p 5432 -e -U postgres xorm_test1 - - createdb -p 5432 -e -U postgres xorm_test2 - - createdb -p 5432 -e -U postgres xorm_test3 - - psql xorm_test postgres -c "create schema xorm" - -test: - override: - # './...' is a relative pattern which means all subdirectories - - go get -u github.com/wadey/gocovmerge - - go test -v -race -db="sqlite3" -conn_str="./test.db" -coverprofile=coverage1-1.txt -covermode=atomic - - go test -v -race -db="sqlite3" -conn_str="./test.db" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic - - go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -coverprofile=coverage2-1.txt -covermode=atomic - - go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic - - go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -coverprofile=coverage3-1.txt -covermode=atomic - - go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic - - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -coverprofile=coverage4-1.txt -covermode=atomic - - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic - - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic - - go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic - - wget -qO- https://binaries.cockroachdb.com/cockroach-v2.1.3.linux-amd64.tgz | tar xvz - - ./cockroach-v2.1.3.linux-amd64/cockroach start --insecure --background --host=localhost - - sleep 5 - - ./cockroach-v2.1.3.linux-amd64/cockroach sql --insecure --execute="create database xorm_test" - - go test -v -race -db="postgres" -conn_str="postgresql://root@localhost:26257/xorm_test?sslmode=disable" -coverprofile=coverage6-1.txt -covermode=atomic - - gocovmerge coverage1-1.txt coverage1-2.txt coverage2-1.txt coverage2-2.txt coverage3-1.txt coverage3-2.txt coverage4-1.txt coverage4-2.txt coverage5-1.txt coverage5-2.txt coverage6-1.txt> coverage.txt - - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./sqlite3.sh - - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./mysql.sh - - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./postgres.sh - post: - - bash <(curl -s https://codecov.io/bash) \ No newline at end of file From 6160c8c670d131237128946e3fd7e41474ab1bc8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Mar 2020 12:45:27 +0000 Subject: [PATCH 048/112] fix bug on deleted with join (#1570) fix bug on deleted with join Reviewed-on: https://gitea.com/xorm/xorm/pulls/1570 --- internal/statements/statement.go | 11 +++++++--- session_find_test.go | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 68738b90..90771d4b 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -992,18 +992,23 @@ 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 + if statement.JoinStr != "" { + colName = statement.quote(statement.TableName()) + + "." + statement.quote(col.Name) + } var cond = builder.NewCond() if col.SQLType.IsNumeric() { - cond = builder.Eq{col.Name: 0} + cond = builder.Eq{colName: 0} } else { // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. if statement.dialect.DBType() != schemas.MSSQL { - cond = builder.Eq{col.Name: utils.ZeroTime1} + cond = builder.Eq{colName: utils.ZeroTime1} } } if col.Nullable { - cond = cond.Or(builder.IsNull{col.Name}) + cond = cond.Or(builder.IsNull{colName}) } return cond diff --git a/session_find_test.go b/session_find_test.go index ad9d1668..4345fcf6 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -833,3 +833,38 @@ func TestJoinFindLimit(t *testing.T) { Limit(10, 10).Find(&finds) assert.NoError(t, err) } + +func TestMoreExtends(t *testing.T) { + type MoreExtendsUsers struct { + ID int64 `xorm:"id autoincr pk" json:"id"` + Name string `xorm:"name not null" json:"name"` + CreatedAt time.Time `xorm:"created not null" json:"created_at"` + UpdatedAt time.Time `xorm:"updated not null" json:"updated_at"` + DeletedAt time.Time `xorm:"deleted" json:"deleted_at"` + } + + type MoreExtendsBooks struct { + ID int64 `xorm:"id autoincr pk" json:"id"` + Name string `xorm:"name not null" json:"name"` + UserID int64 `xorm:"user_id not null" json:"user_id"` + CreatedAt time.Time `xorm:"created not null" json:"created_at"` + UpdatedAt time.Time `xorm:"updated not null" json:"updated_at"` + DeletedAt time.Time `xorm:"deleted" json:"deleted_at"` + } + + type MoreExtendsBooksExtend struct { + MoreExtendsBooks `xorm:"extends"` + Users MoreExtendsUsers `xorm:"extends" json:"users"` + } + + assert.NoError(t, prepareEngine()) + 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"). + Limit(10, 10). + Find(&books) + assert.NoError(t, err) +} From d1cda3f21bc3324490cd7df4bbcdde6653f9cd8d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Mar 2020 13:17:57 +0000 Subject: [PATCH 049/112] oracle support doulbe quote but not square quote (#1571) oracle support doulbe quote but not square quote Reviewed-on: https://gitea.com/xorm/xorm/pulls/1571 --- dialects/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialects/oracle.go b/dialects/oracle.go index 3b9989d9..e5c438bc 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -554,7 +554,7 @@ func (db *oracle) IsReserved(name string) bool { } func (db *oracle) Quoter() schemas.Quoter { - return schemas.Quoter{"[", "]"} + return schemas.Quoter{"\"", "\""} } func (db *oracle) SupportEngine() bool { From 7278e2ab71230027a843eb90ab30943b1df32f4f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 4 Mar 2020 03:30:21 +0000 Subject: [PATCH 050/112] Improve tests (#1572) fix test Improve tests Reviewed-on: https://gitea.com/xorm/xorm/pulls/1572 --- caches/leveldb.go | 12 +- caches/leveldb_test.go | 4 +- convert.go | 125 ++++ engine.go | 40 ++ engine_context.go | 28 - engine_context_test.go | 28 - transancation_test.go => engine_test.go | 14 + error.go | 10 - helpers.go | 163 ----- internal/statements/update.go | 6 +- processors_test.go | 58 +- session.go | 26 + session_cond_test.go | 4 - session_context.go | 23 - session_context_test.go | 36 - session_delete_test.go | 3 +- session_exist_test.go | 25 + session_find_test.go | 140 ++-- session_insert_test.go | 281 ++------ session_pk_test.go | 885 +++++------------------- session_schema_test.go | 10 +- session_tx_test.go | 8 +- session_update_test.go | 355 +++------- statement_test.go | 71 -- tags_test.go | 291 ++------ time_test.go | 7 +- transaction.go | 26 - types_null_test.go | 154 +---- 28 files changed, 738 insertions(+), 2095 deletions(-) delete mode 100644 engine_context.go delete mode 100644 engine_context_test.go rename transancation_test.go => engine_test.go (77%) delete mode 100644 helpers.go delete mode 100644 session_context.go delete mode 100644 session_context_test.go delete mode 100644 statement_test.go delete mode 100644 transaction.go diff --git a/caches/leveldb.go b/caches/leveldb.go index cbaa66b3..d1a177ad 100644 --- a/caches/leveldb.go +++ b/caches/leveldb.go @@ -19,14 +19,14 @@ type LevelDBStore struct { var _ CacheStore = &LevelDBStore{} -func NewLevelDBStore(dbfile string) *LevelDBStore { +func NewLevelDBStore(dbfile string) (*LevelDBStore, error) { db := &LevelDBStore{} - if h, err := leveldb.OpenFile(dbfile, nil); err != nil { - panic(err) - } else { - db.store = h + h, err := leveldb.OpenFile(dbfile, nil) + if err != nil { + return nil, err } - return db + db.store = h + return db, nil } func (s *LevelDBStore) Put(key string, value interface{}) error { diff --git a/caches/leveldb_test.go b/caches/leveldb_test.go index 4b314afc..35981db1 100644 --- a/caches/leveldb_test.go +++ b/caches/leveldb_test.go @@ -11,7 +11,9 @@ import ( ) func TestLevelDBStore(t *testing.T) { - store := NewLevelDBStore("./level.db") + store, err := NewLevelDBStore("./level.db") + assert.NoError(t, err) + var kvs = map[string]interface{}{ "a": "b", } diff --git a/convert.go b/convert.go index 2316ca0b..05db2704 100644 --- a/convert.go +++ b/convert.go @@ -346,3 +346,128 @@ func asBool(bs []byte) (bool, error) { } return strconv.ParseBool(string(bs)) } + +// 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 +} + +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) +} + +func int64ToInt(id int64, tp reflect.Type) interface{} { + return int64ToIntValue(id, tp).Interface() +} diff --git a/engine.go b/engine.go index 7865b7cb..221b7488 100644 --- a/engine.go +++ b/engine.go @@ -1260,3 +1260,43 @@ func (engine *Engine) Unscoped() *Session { func (engine *Engine) tbNameWithSchema(v string) string { return dialects.TableNameWithSchema(engine.dialect, v) } + +// Context creates a session with the context +func (engine *Engine) Context(ctx context.Context) *Session { + session := engine.NewSession() + session.isAutoClose = true + return session.Context(ctx) +} + +// SetDefaultContext set the default context +func (engine *Engine) SetDefaultContext(ctx context.Context) { + engine.defaultContext = ctx +} + +// PingContext tests if database is alive +func (engine *Engine) PingContext(ctx context.Context) error { + session := engine.NewSession() + defer session.Close() + return session.PingContext(ctx) +} + +// Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred +func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) { + session := engine.NewSession() + defer session.Close() + + if err := session.Begin(); err != nil { + return nil, err + } + + result, err := f(session) + if err != nil { + return nil, err + } + + if err := session.Commit(); err != nil { + return nil, err + } + + return result, nil +} diff --git a/engine_context.go b/engine_context.go deleted file mode 100644 index c6cbb76c..00000000 --- a/engine_context.go +++ /dev/null @@ -1,28 +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. - -// +build go1.8 - -package xorm - -import "context" - -// Context creates a session with the context -func (engine *Engine) Context(ctx context.Context) *Session { - session := engine.NewSession() - session.isAutoClose = true - return session.Context(ctx) -} - -// SetDefaultContext set the default context -func (engine *Engine) SetDefaultContext(ctx context.Context) { - engine.defaultContext = ctx -} - -// PingContext tests if database is alive -func (engine *Engine) PingContext(ctx context.Context) error { - session := engine.NewSession() - defer session.Close() - return session.PingContext(ctx) -} diff --git a/engine_context_test.go b/engine_context_test.go deleted file mode 100644 index 1a3276ce..00000000 --- a/engine_context_test.go +++ /dev/null @@ -1,28 +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. - -// +build go1.8 - -package xorm - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestPingContext(t *testing.T) { - assert.NoError(t, prepareEngine()) - - ctx, canceled := context.WithTimeout(context.Background(), time.Nanosecond) - defer canceled() - - time.Sleep(time.Nanosecond) - - err := testEngine.(*Engine).PingContext(ctx) - assert.Error(t, err) - assert.Contains(t, err.Error(), "context deadline exceeded") -} diff --git a/transancation_test.go b/engine_test.go similarity index 77% rename from transancation_test.go rename to engine_test.go index b9a89878..b82ee96a 100644 --- a/transancation_test.go +++ b/engine_test.go @@ -5,6 +5,7 @@ package xorm import ( + "context" "fmt" "testing" "time" @@ -12,6 +13,19 @@ import ( "github.com/stretchr/testify/assert" ) +func TestPingContext(t *testing.T) { + assert.NoError(t, prepareEngine()) + + ctx, canceled := context.WithTimeout(context.Background(), time.Nanosecond) + defer canceled() + + time.Sleep(time.Nanosecond) + + err := testEngine.(*Engine).PingContext(ctx) + assert.Error(t, err) + assert.Contains(t, err.Error(), "context deadline exceeded") +} + func TestAutoTransaction(t *testing.T) { assert.NoError(t, prepareEngine()) diff --git a/error.go b/error.go index a223fc4a..a19860e3 100644 --- a/error.go +++ b/error.go @@ -37,13 +37,3 @@ type ErrFieldIsNotExist struct { func (e ErrFieldIsNotExist) Error() string { return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) } - -// ErrFieldIsNotValid is not valid -type ErrFieldIsNotValid struct { - FieldName string - TableName string -} - -func (e ErrFieldIsNotValid) Error() string { - return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) -} diff --git a/helpers.go b/helpers.go deleted file mode 100644 index e2158c24..00000000 --- a/helpers.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2015 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 ( - "errors" - "fmt" - "reflect" - "strconv" - "time" -) - -// 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 -} - -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) -} - -func int64ToInt(id int64, tp reflect.Type) interface{} { - return int64ToIntValue(id, tp).Interface() -} - -func makeArray(elem string, count int) []string { - res := make([]string, count) - for i := 0; i < count; i++ { - res[i] = elem - } - return res -} - -func rType(bean interface{}) reflect.Type { - sliceValue := reflect.Indirect(reflect.ValueOf(bean)) - // return reflect.TypeOf(sliceValue.Interface()) - return sliceValue.Type() -} - -func structName(v reflect.Type) string { - for v.Kind() == reflect.Ptr { - v = v.Elem() - } - return v.Name() -} - -func formatTime(t time.Time) string { - return t.Format("2006-01-02 15:04:05") -} diff --git a/internal/statements/update.go b/internal/statements/update.go index e9cdd98c..10f36778 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -6,6 +6,7 @@ package statements import ( "database/sql/driver" + "errors" "fmt" "reflect" "time" @@ -200,8 +201,7 @@ func (statement *Statement) BuildUpdates(bean interface{}, continue } } else { - // TODO: how to handler? - panic("not supported") + return nil, nil, errors.New("Not supported multiple primary keys") } } } else { @@ -209,7 +209,7 @@ func (statement *Statement) BuildUpdates(bean interface{}, if requiredField || !utils.IsStructZero(fieldValue) { bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { - panic(fmt.Sprintf("mashal %v failed", fieldValue.Interface())) + return nil, nil, fmt.Errorf("mashal %v failed", fieldValue.Interface()) } if col.SQLType.IsText() { val = string(bytes) diff --git a/processors_test.go b/processors_test.go index d1efc047..c0f962d3 100644 --- a/processors_test.go +++ b/processors_test.go @@ -125,17 +125,11 @@ func TestProcessors(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&ProcessorsStruct{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) p := &ProcessorsStruct{} err = testEngine.CreateTables(&ProcessorsStruct{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) b4InsertFunc := func(bean interface{}) { if v, ok := (bean).(*ProcessorsStruct); ok { @@ -259,42 +253,22 @@ func TestProcessors(t *testing.T) { _, err = testEngine.Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) assert.NoError(t, err) - if p.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p.AfterUpdatedFlag == 0 { - t.Error(errors.New("AfterUpdatedFlag not set")) - } - if p.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p.AfterUpdatedViaExt == 0 { - t.Error(errors.New("AfterUpdatedViaExt not set")) - } + assert.False(t, p.B4UpdateFlag == 0, "B4UpdateFlag not set") + assert.False(t, p.AfterUpdatedFlag == 0, "AfterUpdatedFlag not set") + assert.False(t, p.B4UpdateViaExt == 0, "B4UpdateViaExt not set") + assert.False(t, p.AfterUpdatedViaExt == 0, "AfterUpdatedViaExt not set") p2 = &ProcessorsStruct{} has, err = testEngine.ID(p.Id).Get(p2) assert.NoError(t, err) assert.True(t, has) - if p2.B4UpdateFlag == 0 { - t.Error(errors.New("B4UpdateFlag not set")) - } - if p2.AfterUpdatedFlag != 0 { - t.Error(errors.New("AfterUpdatedFlag is set: " + string(p.AfterUpdatedFlag))) - } - if p2.B4UpdateViaExt == 0 { - t.Error(errors.New("B4UpdateViaExt not set")) - } - if p2.AfterUpdatedViaExt != 0 { - t.Error(errors.New("AfterUpdatedViaExt is set: " + string(p.AfterUpdatedViaExt))) - } - if p2.BeforeSetFlag != 9 { - t.Error(fmt.Errorf("BeforeSetFlag is %d not 9", p2.BeforeSetFlag)) - } - if p2.AfterSetFlag != 9 { - t.Error(fmt.Errorf("AfterSetFlag is %d not 9", p2.BeforeSetFlag)) - } + assert.False(t, p2.B4UpdateFlag == 0, "B4UpdateFlag not set") + assert.False(t, p2.AfterUpdatedFlag != 0, fmt.Sprintf("AfterUpdatedFlag is set: %d", p.AfterUpdatedFlag)) + assert.False(t, p2.B4UpdateViaExt == 0, "B4UpdateViaExt not set") + assert.False(t, p2.AfterUpdatedViaExt != 0, fmt.Sprintf("AfterUpdatedViaExt is set: %d", p.AfterUpdatedViaExt)) + assert.False(t, p2.BeforeSetFlag != 9, fmt.Sprintf("BeforeSetFlag is %d not 9", p2.BeforeSetFlag)) + assert.False(t, p2.AfterSetFlag != 9, fmt.Sprintf("AfterSetFlag is %d not 9", p2.BeforeSetFlag)) // -- // test delete processors @@ -450,12 +424,7 @@ func TestProcessorsTx(t *testing.T) { p2 := &ProcessorsStruct{} _, err = testEngine.ID(p.Id).Get(p2) assert.NoError(t, err) - - if p2.Id > 0 { - err = errors.New("tx got committed upon insert!?") - t.Error(err) - panic(err) - } + assert.False(t, p2.Id > 0, "tx got committed upon insert!?") // -- // test insert processors with tx commit @@ -846,7 +815,6 @@ func TestProcessorsTx(t *testing.T) { t.Error(errors.New("AfterUpdatedFlag set")) } session.Close() - // -- } type AfterLoadStructA struct { diff --git a/session.go b/session.go index 4cab103f..db990684 100644 --- a/session.go +++ b/session.go @@ -22,6 +22,16 @@ import ( "xorm.io/xorm/schemas" ) +// ErrFieldIsNotValid is not valid +type ErrFieldIsNotValid struct { + FieldName string + TableName string +} + +func (e ErrFieldIsNotValid) Error() string { + return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) +} + type sessionType int const ( @@ -883,3 +893,19 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) { fieldValue.SetUint(fieldValue.Uint() + 1) } } + +// Context sets the context on this session +func (session *Session) Context(ctx context.Context) *Session { + session.ctx = ctx + return session +} + +// PingContext test if database is ok +func (session *Session) PingContext(ctx context.Context) error { + if session.isAutoClose { + defer session.Close() + } + + session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName()) + return session.DB().PingContext(ctx) +} diff --git a/session_cond_test.go b/session_cond_test.go index 30b9f778..8fb7afc3 100644 --- a/session_cond_test.go +++ b/session_cond_test.go @@ -139,13 +139,11 @@ func TestIn(t *testing.T) { users := make([]Userinfo, 0) err = testEngine.In("id", ids[0], ids[1], ids[2]).Find(&users) assert.NoError(t, err) - fmt.Println(users) assert.EqualValues(t, 3, len(users)) users = make([]Userinfo, 0) err = testEngine.In("id", ids).Find(&users) assert.NoError(t, err) - fmt.Println(users) assert.EqualValues(t, 3, len(users)) for _, user := range users { @@ -163,7 +161,6 @@ func TestIn(t *testing.T) { err = testEngine.Where(department+" = ?", "dev").In("id", idsInterface...).Find(&users) assert.NoError(t, err) - fmt.Println(users) assert.EqualValues(t, 3, len(users)) for _, user := range users { @@ -177,7 +174,6 @@ func TestIn(t *testing.T) { err = testEngine.In("id", 1).In("id", 2).In(department, dev).Find(&users) assert.NoError(t, err) - fmt.Println(users) cnt, err = testEngine.In("id", ids[0]).Update(&Userinfo{Departname: "dev-"}) assert.NoError(t, err) diff --git a/session_context.go b/session_context.go deleted file mode 100644 index 915f0568..00000000 --- a/session_context.go +++ /dev/null @@ -1,23 +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 xorm - -import "context" - -// Context sets the context on this session -func (session *Session) Context(ctx context.Context) *Session { - session.ctx = ctx - return session -} - -// PingContext test if database is ok -func (session *Session) PingContext(ctx context.Context) error { - if session.isAutoClose { - defer session.Close() - } - - session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName()) - return session.DB().PingContext(ctx) -} diff --git a/session_context_test.go b/session_context_test.go deleted file mode 100644 index 2784468d..00000000 --- a/session_context_test.go +++ /dev/null @@ -1,36 +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 xorm - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestQueryContext(t *testing.T) { - type ContextQueryStruct struct { - Id int64 - Name string - } - - assert.NoError(t, prepareEngine()) - assertSync(t, new(ContextQueryStruct)) - - _, err := testEngine.Insert(&ContextQueryStruct{Name: "1"}) - assert.NoError(t, err) - - ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) - defer cancel() - - time.Sleep(time.Nanosecond) - - has, err := testEngine.Context(ctx).Exist(&ContextQueryStruct{Name: "1"}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "context deadline exceeded") - assert.False(t, has) -} diff --git a/session_delete_test.go b/session_delete_test.go index 3d0fa1a8..d7fb3110 100644 --- a/session_delete_test.go +++ b/session_delete_test.go @@ -8,9 +8,10 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "xorm.io/xorm/caches" "xorm.io/xorm/schemas" + + "github.com/stretchr/testify/assert" ) func TestDelete(t *testing.T) { diff --git a/session_exist_test.go b/session_exist_test.go index 2792654f..0861382a 100644 --- a/session_exist_test.go +++ b/session_exist_test.go @@ -5,7 +5,9 @@ package xorm import ( + "context" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -181,3 +183,26 @@ func TestExistStructForJoin(t *testing.T) { assert.NoError(t, err) assert.True(t, has) } + +func TestExistContext(t *testing.T) { + type ContextQueryStruct struct { + Id int64 + Name string + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(ContextQueryStruct)) + + _, err := testEngine.Insert(&ContextQueryStruct{Name: "1"}) + assert.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) + defer cancel() + + time.Sleep(time.Nanosecond) + + has, err := testEngine.Context(ctx).Exist(&ContextQueryStruct{Name: "1"}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "context deadline exceeded") + assert.False(t, has) +} diff --git a/session_find_test.go b/session_find_test.go index 4345fcf6..2d15eed8 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "testing" "time" @@ -79,17 +78,11 @@ func TestWhere(t *testing.T) { users := make([]Userinfo, 0) err := testEngine.Where("id > ?", 2).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) fmt.Println(users) err = testEngine.Where("id > ?", 2).And("id < ?", 10).Find(&users) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) fmt.Println(users) } @@ -200,13 +193,7 @@ func TestFindMap(t *testing.T) { users := make(map[int64]Userinfo) err := testEngine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for _, user := range users { - fmt.Println(user) - } + assert.NoError(t, err) } func TestFindMap2(t *testing.T) { @@ -215,13 +202,7 @@ func TestFindMap2(t *testing.T) { users := make(map[int64]*Userinfo) err := testEngine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - for id, user := range users { - fmt.Println(id, user) - } + assert.NoError(t, err) } func TestDistinct(t *testing.T) { @@ -239,8 +220,6 @@ func TestDistinct(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(users)) - fmt.Println(users) - type Depart struct { Departname string } @@ -248,12 +227,7 @@ func TestDistinct(t *testing.T) { users2 := make([]Depart, 0) err = testEngine.Distinct(departname).Table(new(Userinfo)).Find(&users2) assert.NoError(t, err) - if len(users2) != 1 { - fmt.Println(len(users2)) - t.Error(err) - panic(errors.New("should be one record")) - } - fmt.Println(users2) + assert.EqualValues(t, 1, len(users2)) } func TestOrder(t *testing.T) { @@ -263,12 +237,10 @@ func TestOrder(t *testing.T) { users := make([]Userinfo, 0) err := testEngine.OrderBy("id desc").Find(&users) assert.NoError(t, err) - fmt.Println(users) users2 := make([]Userinfo, 0) err = testEngine.Asc("id", "username").Desc("height").Find(&users2) assert.NoError(t, err) - fmt.Println(users2) } func TestGroupBy(t *testing.T) { @@ -287,15 +259,6 @@ func TestHaving(t *testing.T) { users := make([]Userinfo, 0) err := testEngine.GroupBy("username").Having("username='xlw'").Find(&users) assert.NoError(t, err) - fmt.Println(users) - - /*users = make([]Userinfo, 0) - err = testEngine.Cols("id, username").GroupBy("username").Having("username='xlw'").Find(&users) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(users)*/ } func TestOrderSameMapper(t *testing.T) { @@ -315,12 +278,10 @@ func TestOrderSameMapper(t *testing.T) { users := make([]Userinfo, 0) err := testEngine.OrderBy("id desc").Find(&users) assert.NoError(t, err) - fmt.Println(users) users2 := make([]Userinfo, 0) err = testEngine.Asc("id", "Username").Desc("Height").Find(&users2) assert.NoError(t, err) - fmt.Println(users2) } func TestHavingSameMapper(t *testing.T) { @@ -337,10 +298,7 @@ func TestHavingSameMapper(t *testing.T) { users := make([]Userinfo, 0) err := testEngine.GroupBy("`Username`").Having("`Username`='xlw'").Find(&users) - if err != nil { - t.Fatal(err) - } - fmt.Println(users) + assert.NoError(t, err) } func TestFindInts(t *testing.T) { @@ -350,39 +308,24 @@ func TestFindInts(t *testing.T) { userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") var idsInt64 []int64 err := testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsInt64) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsInt64) + assert.NoError(t, err) var idsInt32 []int32 err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsInt32) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsInt32) + assert.NoError(t, err) var idsInt []int err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsInt) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsInt) + assert.NoError(t, err) var idsUint []uint err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsUint) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsUint) + assert.NoError(t, err) type MyInt int var idsMyInt []MyInt err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsMyInt) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsMyInt) + assert.NoError(t, err) } func TestFindStrings(t *testing.T) { @@ -392,10 +335,7 @@ func TestFindStrings(t *testing.T) { username := testEngine.GetColumnMapper().Obj2Table("Username") var idsString []string err := testEngine.Table(userinfo).Cols(username).Desc("id").Find(&idsString) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsString) + assert.NoError(t, err) } func TestFindMyString(t *testing.T) { @@ -406,10 +346,7 @@ func TestFindMyString(t *testing.T) { var idsMyString []MyString err := testEngine.Table(userinfo).Cols(username).Desc("id").Find(&idsMyString) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsMyString) + assert.NoError(t, err) } func TestFindInterface(t *testing.T) { @@ -420,10 +357,7 @@ func TestFindInterface(t *testing.T) { username := testEngine.GetColumnMapper().Obj2Table("Username") var idsInterface []interface{} err := testEngine.Table(userinfo).Cols(username).Desc("id").Find(&idsInterface) - if err != nil { - t.Fatal(err) - } - fmt.Println(idsInterface) + assert.NoError(t, err) } func TestFindSliceBytes(t *testing.T) { @@ -433,12 +367,7 @@ func TestFindSliceBytes(t *testing.T) { userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") var ids [][][]byte err := testEngine.Table(userinfo).Desc("id").Find(&ids) - if err != nil { - t.Fatal(err) - } - for _, record := range ids { - fmt.Println(record) - } + assert.NoError(t, err) } func TestFindSlicePtrString(t *testing.T) { @@ -448,12 +377,7 @@ func TestFindSlicePtrString(t *testing.T) { userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") var ids [][]*string err := testEngine.Table(userinfo).Desc("id").Find(&ids) - if err != nil { - t.Fatal(err) - } - for _, record := range ids { - fmt.Println(record) - } + assert.NoError(t, err) } func TestFindMapBytes(t *testing.T) { @@ -463,12 +387,7 @@ func TestFindMapBytes(t *testing.T) { userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") var ids []map[string][]byte err := testEngine.Table(userinfo).Desc("id").Find(&ids) - if err != nil { - t.Fatal(err) - } - for _, record := range ids { - fmt.Println(record) - } + assert.NoError(t, err) } func TestFindMapPtrString(t *testing.T) { @@ -479,9 +398,6 @@ func TestFindMapPtrString(t *testing.T) { var ids []map[string]*string err := testEngine.Table(userinfo).Desc("id").Find(&ids) assert.NoError(t, err) - for _, record := range ids { - fmt.Println(record) - } } func TestFindBit(t *testing.T) { @@ -868,3 +784,25 @@ func TestMoreExtends(t *testing.T) { Find(&books) assert.NoError(t, err) } + +func TestDistinctAndCols(t *testing.T) { + type DistinctAndCols struct { + Id int64 + Name string + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(DistinctAndCols)) + + cnt, err := testEngine.Insert(&DistinctAndCols{ + Name: "test", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var names []string + err = testEngine.Table("distinct_and_cols").Cols("name").Distinct("name").Find(&names) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(names)) + assert.EqualValues(t, "test", names[0]) +} diff --git a/session_insert_test.go b/session_insert_test.go index 72b89e09..09f647b8 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "reflect" "testing" @@ -154,9 +153,6 @@ func TestInsert(t *testing.T) { // Username is unique, so this should return error assert.Error(t, err, "insert should fail but no error returned") assert.EqualValues(t, 0, cnt, "insert not returned 1") - if err == nil { - panic("should return err") - } } func TestInsertAutoIncr(t *testing.T) { @@ -167,19 +163,9 @@ func TestInsertAutoIncr(t *testing.T) { user := Userinfo{Username: "xiaolunwen2", Departname: "dev", Alias: "lunny", Created: time.Now(), Detail: Userdetail{Id: 1}, Height: 1.78, Avatar: []byte{1, 2, 3}, IsMan: true} cnt, err := testEngine.Insert(&user) - fmt.Println(user.Uid) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert not returned 1") - t.Error(err) - panic(err) - } - if user.Uid <= 0 { - t.Error(errors.New("not return id error")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.Greater(t, user.Uid, int64(0)) } type DefaultInsert struct { @@ -203,26 +189,10 @@ func TestInsertDefault(t *testing.T) { has, err := testEngine.Desc("id").Get(di) assert.NoError(t, err) - if !has { - err = errors.New("error with no data") - t.Error(err) - panic(err) - } - if di.Status != -1 { - err = errors.New("inserted error data") - t.Error(err) - panic(err) - } - if di2.Updated.Unix() != di.Updated.Unix() { - err = errors.New("updated should equal") - t.Error(err, di.Updated, di2.Updated) - panic(err) - } - if di2.Created.Unix() != di.Created.Unix() { - err = errors.New("created should equal") - t.Error(err, di.Created, di2.Created) - panic(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()) } type DefaultInsert2 struct { @@ -237,53 +207,20 @@ func TestInsertDefault2(t *testing.T) { di := new(DefaultInsert2) err := testEngine.Sync2(di) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) var di2 = DefaultInsert2{Name: "test"} _, err = testEngine.Omit(testEngine.GetColumnMapper().Obj2Table("CheckTime")).Insert(&di2) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) has, err := testEngine.Desc("id").Get(di) - if err != nil { - t.Error(err) - } - if !has { - err = errors.New("error with no data") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) has, err = testEngine.NoAutoCondition().Desc("id").Get(&di2) - if err != nil { - t.Error(err) - } - - if !has { - err = errors.New("error with no data") - t.Error(err) - panic(err) - } - - if *di != di2 { - err = fmt.Errorf("%v is not equal to %v", di, di2) - t.Error(err) - panic(err) - } - - /*if di2.Updated.Unix() != di.Updated.Unix() { - err = errors.New("updated should equal") - t.Error(err, di.Updated, di2.Updated) - panic(err) - } - if di2.Created.Unix() != di.Created.Unix() { - err = errors.New("created should equal") - t.Error(err, di.Created, di2.Created) - panic(err) - }*/ + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, *di, di2) } type CreatedInsert struct { @@ -321,138 +258,82 @@ func TestInsertCreated(t *testing.T) { di := new(CreatedInsert) err := testEngine.Sync2(di) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci := &CreatedInsert{} _, err = testEngine.Insert(ci) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) has, err := testEngine.Desc("id").Get(di) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci.Created.Unix() != di.Created.Unix() { - t.Fatal("should equal:", ci, di) - } - fmt.Println("ci:", ci, "di:", di) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci.Created.Unix(), di.Created.Unix()) di2 := new(CreatedInsert2) err = testEngine.Sync2(di2) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci2 := &CreatedInsert2{} _, err = testEngine.Insert(ci2) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + has, err = testEngine.Desc("id").Get(di2) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci2.Created != di2.Created { - t.Fatal("should equal:", ci2, di2) - } - fmt.Println("ci2:", ci2, "di2:", di2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci2.Created, di2.Created) di3 := new(CreatedInsert3) err = testEngine.Sync2(di3) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci3 := &CreatedInsert3{} _, err = testEngine.Insert(ci3) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + has, err = testEngine.Desc("id").Get(di3) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci3.Created != di3.Created { - t.Fatal("should equal:", ci3, di3) - } - fmt.Println("ci3:", ci3, "di3:", di3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci3.Created, di3.Created) di4 := new(CreatedInsert4) err = testEngine.Sync2(di4) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci4 := &CreatedInsert4{} _, err = testEngine.Insert(ci4) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + has, err = testEngine.Desc("id").Get(di4) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci4.Created != di4.Created { - t.Fatal("should equal:", ci4, di4) - } - fmt.Println("ci4:", ci4, "di4:", di4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci4.Created, di4.Created) di5 := new(CreatedInsert5) err = testEngine.Sync2(di5) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci5 := &CreatedInsert5{} _, err = testEngine.Insert(ci5) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + has, err = testEngine.Desc("id").Get(di5) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci5.Created.Unix() != di5.Created.Unix() { - t.Fatal("should equal:", ci5, di5) - } - fmt.Println("ci5:", ci5, "di5:", di5) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci5.Created.Unix(), di5.Created.Unix()) di6 := new(CreatedInsert6) err = testEngine.Sync2(di6) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + oldTime := time.Now().Add(-time.Hour) ci6 := &CreatedInsert6{Created: oldTime} _, err = testEngine.Insert(ci6) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) has, err = testEngine.Desc("id").Get(di6) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci6.Created.Unix() != di6.Created.Unix() { - t.Fatal("should equal:", ci6, di6) - } - fmt.Println("ci6:", ci6, "di6:", di6) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci6.Created.Unix(), di6.Created.Unix()) } type JsonTime time.Time @@ -509,25 +390,16 @@ func TestCreatedJsonTime(t *testing.T) { di5 := new(MyJsonTime) err := testEngine.Sync2(di5) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci5 := &MyJsonTime{} _, err = testEngine.Insert(ci5) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + has, err := testEngine.Desc("id").Get(di5) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if time.Time(ci5.Created).Unix() != time.Time(di5.Created).Unix() { - t.Fatal("should equal:", time.Time(ci5.Created).Unix(), time.Time(di5.Created).Unix()) - } - fmt.Println("ci5:", ci5, "di5:", di5) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, time.Time(ci5.Created).Unix(), time.Time(di5.Created).Unix()) var dis = make([]MyJsonTime, 0) err = testEngine.Find(&dis) @@ -546,10 +418,7 @@ func TestInsertMulti2(t *testing.T) { {Username: "xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, } cnt, err := testEngine.Insert(&users) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) assert.EqualValues(t, len(users), cnt) users2 := []*Userinfo{ @@ -573,28 +442,10 @@ func TestInsertTwoTable(t *testing.T) { userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now(), Detail: userdetail} cnt, err := testEngine.Insert(&userinfo, &userdetail) - if err != nil { - t.Error(err) - panic(err) - } - - if userinfo.Uid <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - if userdetail.Id <= 0 { - err = errors.New("not return id error") - t.Error(err) - panic(err) - } - - if cnt != 2 { - err = errors.New("insert not returned 2") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.Greater(t, userinfo.Uid, int64(0)) + assert.Greater(t, userdetail.Id, int64(0)) + assert.EqualValues(t, 2, cnt) } func TestInsertCreatedInt64(t *testing.T) { diff --git a/session_pk_test.go b/session_pk_test.go index ec5a611b..8a886603 100644 --- a/session_pk_test.go +++ b/session_pk_test.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "sort" "testing" "time" @@ -70,626 +69,280 @@ func TestIntId(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&IntId{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&IntId{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&IntId{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) bean := new(IntId) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]IntId, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) beans2 := make(map[int]IntId) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&IntId{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestInt16Id(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Int16Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Int16Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&Int16Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) bean := new(Int16Id) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]Int16Id, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) beans2 := make(map[int16]Int16Id, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&Int16Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestInt32Id(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Int32Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Int32Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&Int32Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) bean := new(Int32Id) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]Int32Id, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) beans2 := make(map[int32]Int32Id, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&Int32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestUintId(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&UintId{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&UintId{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&UintId{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) var inserts = []UintId{ {Name: "test1"}, {Name: "test2"}, } cnt, err = testEngine.Insert(&inserts) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 2 { - err = errors.New("insert count should be two") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) bean := new(UintId) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]UintId, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 3 { - err = errors.New("get count should be three") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 3, len(beans)) beans2 := make(map[uint]UintId, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 3 { - err = errors.New("get count should be three") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 3, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&UintId{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestUint16Id(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Uint16Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Uint16Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&Uint16Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, cnt) bean := new(Uint16Id) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]Uint16Id, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) beans2 := make(map[uint16]Uint16Id, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&Uint16Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestUint32Id(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Uint32Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Uint32Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&Uint32Id{Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, cnt) bean := new(Uint32Id) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]Uint32Id, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) beans2 := make(map[uint32]Uint32Id, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&Uint32Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestUint64Id(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Uint64Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Uint64Id{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) idbean := &Uint64Id{Name: "test"} cnt, err := testEngine.Insert(idbean) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, cnt) bean := new(Uint64Id) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if bean.Id != idbean.Id { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, bean.Id, idbean.Id) beans := make([]Uint64Id, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if *bean != beans[0] { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) + assert.EqualValues(t, *bean, beans[0]) beans2 := make(map[uint64]Uint64Id, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if *bean != beans2[bean.Id] { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) + assert.EqualValues(t, *bean, beans2[bean.Id]) cnt, err = testEngine.ID(bean.Id).Delete(&Uint64Id{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestStringPK(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&StringPK{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&StringPK{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&StringPK{Id: "1-1-2", Name: "test"}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, cnt) bean := new(StringPK) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) beans := make([]StringPK, 0) err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) beans2 := make(map[string]StringPK) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) cnt, err = testEngine.ID(bean.Id).Delete(&StringPK{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } type CompositeKey struct { @@ -702,68 +355,40 @@ func TestCompositeKey(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&CompositeKey{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&CompositeKey{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&CompositeKey{11, 22, ""}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("failed to insert CompositeKey{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cnt, err = testEngine.Insert(&CompositeKey{11, 22, ""}) - if err == nil || cnt == 1 { - t.Error(errors.New("inserted CompositeKey{11, 22}")) - } + assert.Error(t, err) + assert.NotEqual(t, int64(1), cnt) var compositeKeyVal CompositeKey has, err := testEngine.ID(schemas.PK{11, 22}).Get(&compositeKeyVal) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get CompositeKey{11, 22}")) - } + assert.NoError(t, err) + assert.True(t, has) var compositeKeyVal2 CompositeKey // test passing PK ptr, this test seem failed withCache has, err = testEngine.ID(&schemas.PK{11, 22}).Get(&compositeKeyVal2) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get CompositeKey{11, 22}")) - } - - if compositeKeyVal != compositeKeyVal2 { - t.Error(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, compositeKeyVal, compositeKeyVal2) var cps = make([]CompositeKey, 0) err = testEngine.Find(&cps) - if err != nil { - t.Error(err) - } - if len(cps) != 1 { - t.Error(errors.New("should has one record")) - } - if cps[0] != compositeKeyVal { - t.Error(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(cps)) + assert.EqualValues(t, cps[0], compositeKeyVal) cnt, err = testEngine.Insert(&CompositeKey{22, 22, ""}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("failed to insert CompositeKey{22, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cps = make([]CompositeKey, 0) err = testEngine.Find(&cps) @@ -773,18 +398,12 @@ func TestCompositeKey(t *testing.T) { compositeKeyVal = CompositeKey{UpdateStr: "test1"} cnt, err = testEngine.ID(schemas.PK{11, 22}).Update(&compositeKeyVal) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't update CompositeKey{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cnt, err = testEngine.ID(schemas.PK{11, 22}).Delete(&CompositeKey{}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't delete CompositeKey{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestCompositeKey2(t *testing.T) { @@ -798,60 +417,37 @@ func TestCompositeKey2(t *testing.T) { } err := testEngine.DropTables(&User{}) - - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&User{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&User{"11", "nick", 22, 5}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("failed to insert User{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cnt, err = testEngine.Insert(&User{"11", "nick", 22, 6}) - if err == nil || cnt == 1 { - t.Error(errors.New("inserted User{11, 22}")) - } + assert.Error(t, err) + assert.NotEqual(t, 1, cnt) var user User has, err := testEngine.ID(schemas.PK{"11", 22}).Get(&user) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get User{11, 22}")) - } + assert.NoError(t, err) + assert.True(t, has) // test passing PK ptr, this test seem failed withCache has, err = testEngine.ID(&schemas.PK{"11", 22}).Get(&user) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get User{11, 22}")) - } + assert.NoError(t, err) + assert.True(t, has) user = User{NickName: "test1"} cnt, err = testEngine.ID(schemas.PK{"11", 22}).Update(&user) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't update User{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cnt, err = testEngine.ID(schemas.PK{"11", 22}).Delete(&User{}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't delete CompositeKey{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } type MyString string @@ -867,235 +463,112 @@ func TestCompositeKey3(t *testing.T) { err := testEngine.DropTables(&UserPK2{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&UserPK2{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.Insert(&UserPK2{"11", "nick", 22, 5}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("failed to insert User{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cnt, err = testEngine.Insert(&UserPK2{"11", "nick", 22, 6}) - if err == nil || cnt == 1 { - t.Error(errors.New("inserted User{11, 22}")) - } + assert.Error(t, err) + assert.NotEqual(t, 1, cnt) var user UserPK2 has, err := testEngine.ID(schemas.PK{"11", 22}).Get(&user) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get User{11, 22}")) - } + assert.NoError(t, err) + assert.True(t, has) // test passing PK ptr, this test seem failed withCache has, err = testEngine.ID(&schemas.PK{"11", 22}).Get(&user) - if err != nil { - t.Error(err) - } else if !has { - t.Error(errors.New("can't get User{11, 22}")) - } + assert.NoError(t, err) + assert.True(t, has) user = UserPK2{NickName: "test1"} cnt, err = testEngine.ID(schemas.PK{"11", 22}).Update(&user) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't update User{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) cnt, err = testEngine.ID(schemas.PK{"11", 22}).Delete(&UserPK2{}) - if err != nil { - t.Error(err) - } else if cnt != 1 { - t.Error(errors.New("can't delete CompositeKey{11, 22}")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestMyIntId(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&MyIntPK{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&MyIntPK{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) idbean := &MyIntPK{Name: "test"} cnt, err := testEngine.Insert(idbean) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, cnt) bean := new(MyIntPK) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if bean.ID != idbean.ID { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, bean.ID, idbean.ID) var beans []MyIntPK err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if *bean != beans[0] { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) + assert.EqualValues(t, *bean, beans[0]) beans2 := make(map[ID]MyIntPK, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if *bean != beans2[bean.ID] { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) + assert.EqualValues(t, *bean, beans2[bean.ID]) cnt, err = testEngine.ID(bean.ID).Delete(&MyIntPK{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestMyStringId(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&MyStringPK{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&MyStringPK{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) idbean := &MyStringPK{ID: "1111", Name: "test"} cnt, err := testEngine.Insert(idbean) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) bean := new(MyStringPK) has, err := testEngine.Get(bean) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if bean.ID != idbean.ID { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, bean.ID, idbean.ID) var beans []MyStringPK err = testEngine.Find(&beans) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if *bean != beans[0] { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans)) + assert.EqualValues(t, *bean, beans[0]) beans2 := make(map[StrID]MyStringPK, 0) err = testEngine.Find(&beans2) - if err != nil { - t.Error(err) - panic(err) - } - if len(beans2) != 1 { - err = errors.New("get count should be one") - t.Error(err) - panic(err) - } - - if *bean != beans2[bean.ID] { - panic(errors.New("should be equal")) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, len(beans2)) + assert.EqualValues(t, *bean, beans2[bean.ID]) cnt, err = testEngine.ID(bean.ID).Delete(&MyStringPK{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("insert count should be one") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) } func TestSingleAutoIncrColumn(t *testing.T) { diff --git a/session_schema_test.go b/session_schema_test.go index 86f9315e..a20a1f97 100644 --- a/session_schema_test.go +++ b/session_schema_test.go @@ -260,16 +260,10 @@ func TestCharst(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables("user_charset") - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.Charset("utf8").Table("user_charset").CreateTable(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } func TestSync2_1(t *testing.T) { diff --git a/session_tx_test.go b/session_tx_test.go index 303fd8d6..d95adead 100644 --- a/session_tx_test.go +++ b/session_tx_test.go @@ -53,9 +53,7 @@ func TestCombineTransaction(t *testing.T) { counter := func() { total, err := testEngine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) fmt.Printf("----now total %v records\n", total) } @@ -97,9 +95,7 @@ func TestCombineTransactionSameMapper(t *testing.T) { counter := func() { total, err := testEngine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) fmt.Printf("----now total %v records\n", total) } diff --git a/session_update_test.go b/session_update_test.go index f76e447b..d65e1207 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "sync" "testing" @@ -277,10 +276,7 @@ func TestUpdateMap2(t *testing.T) { _, err := testEngine.Table("update_must_cols").Where("id =?", 1).Update(map[string]interface{}{ "bool": true, }) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } func TestUpdate1(t *testing.T) { @@ -293,14 +289,8 @@ func TestUpdate1(t *testing.T) { var ori Userinfo has, err := testEngine.Get(&ori) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - t.Error(errors.New("not exist")) - panic(errors.New("not exist")) - } + assert.NoError(t, err) + assert.True(t, has) // update by id user := Userinfo{Username: "xxx", Height: 1.2} @@ -324,10 +314,7 @@ func TestUpdate1(t *testing.T) { { user := &Userinfo{Username: "not null data", Height: 180.5} _, err := testEngine.Insert(user) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) userID := user.Uid has, err := testEngine.ID(userID). @@ -337,29 +324,15 @@ func TestUpdate1(t *testing.T) { And("detail_id = ?", 0). And("is_man = ?", 0). Get(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("cannot insert properly") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has, "cannot insert properly") updatedUser := &Userinfo{Username: "null data"} cnt, err = testEngine.ID(userID). Nullable("height", "departname", "is_man", "created"). Update(updatedUser) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update not returned 1") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt, "update not returned 1") has, err = testEngine.ID(userID). And("username = ?", updatedUser.Username). @@ -369,60 +342,27 @@ func TestUpdate1(t *testing.T) { And("created IS NULL"). And("detail_id = ?", 0). Get(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("cannot update with null properly") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has, "cannot update with null properly") cnt, err = testEngine.ID(userID).Delete(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("delete not returned 1") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt, "delete not returned 1") } err = testEngine.StoreEngine("Innodb").Sync2(&Article{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) defer func() { err = testEngine.DropTables(&Article{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) }() a := &Article{0, "1", "2", "3", "4", "5", 2} cnt, err = testEngine.Insert(a) - if err != nil { - t.Error(err) - panic(err) - } - - if cnt != 1 { - err = fmt.Errorf("insert not returned 1 but %d", cnt) - t.Error(err) - panic(err) - } - - if a.Id == 0 { - err = errors.New("insert returned id is 0") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt, fmt.Sprintf("insert not returned 1 but %d", cnt)) + assert.Greater(t, a.Id, int32(0), "insert returned id is 0") cnt, err = testEngine.ID(a.Id).Update(&Article{Name: "6"}) assert.NoError(t, err) @@ -448,28 +388,18 @@ func TestUpdate1(t *testing.T) { assert.EqualValues(t, *col2, *col3) { - col1 := &UpdateMustCols{} err = testEngine.Sync(col1) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) _, err = testEngine.Insert(col1) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) col2 := &UpdateMustCols{col1.Id, true, ""} boolStr := testEngine.GetColumnMapper().Obj2Table("Bool") stringStr := testEngine.GetColumnMapper().Obj2Table("String") _, err = testEngine.ID(col2.Id).MustCols(boolStr, stringStr).Update(col2) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) col3 := &UpdateMustCols{} has, err := testEngine.ID(col2.Id).Get(col3) @@ -547,32 +477,19 @@ func TestUpdateUpdated(t *testing.T) { di := new(UpdatedUpdate) err := testEngine.Sync2(di) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate{}) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) ci := &UpdatedUpdate{} _, err = testEngine.ID(1).Update(ci) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) has, err := testEngine.ID(1).Get(di) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci.Updated.Unix() != di.Updated.Unix() { - t.Fatal("should equal:", ci, di) - } - fmt.Println("ci:", ci, "di:", di) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci.Updated.Unix(), di.Updated.Unix()) di2 := new(UpdatedUpdate2) err = testEngine.Sync2(di2) @@ -603,88 +520,51 @@ func TestUpdateUpdated(t *testing.T) { di3 := new(UpdatedUpdate3) err = testEngine.Sync2(di3) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate3{}) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci3 := &UpdatedUpdate3{} _, err = testEngine.ID(1).Update(ci3) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) has, err = testEngine.ID(1).Get(di3) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci3.Updated != di3.Updated { - t.Fatal("should equal:", ci3, di3) - } - fmt.Println("ci3:", ci3, "di3:", di3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci3.Updated, di3.Updated) di4 := new(UpdatedUpdate4) err = testEngine.Sync2(di4) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate4{}) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) ci4 := &UpdatedUpdate4{} _, err = testEngine.ID(1).Update(ci4) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) has, err = testEngine.ID(1).Get(di4) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci4.Updated != di4.Updated { - t.Fatal("should equal:", ci4, di4) - } - fmt.Println("ci4:", ci4, "di4:", di4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci4.Updated, di4.Updated) di5 := new(UpdatedUpdate5) err = testEngine.Sync2(di5) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) _, err = testEngine.Insert(&UpdatedUpdate5{}) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) + ci5 := &UpdatedUpdate5{} _, err = testEngine.ID(1).Update(ci5) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) has, err = testEngine.ID(1).Get(di5) - if err != nil { - t.Fatal(err) - } - if !has { - t.Fatal(ErrNotExist) - } - if ci5.Updated.Unix() != di5.Updated.Unix() { - t.Fatal("should equal:", ci5, di5) - } - fmt.Println("ci5:", ci5, "di5:", di5) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, ci5.Updated.Unix(), di5.Updated.Unix()) } func TestUpdateSameMapper(t *testing.T) { @@ -749,18 +629,8 @@ func TestUpdateSameMapper(t *testing.T) { a := &Article{0, "1", "2", "3", "4", "5", 2} cnt, err = testEngine.Insert(a) assert.NoError(t, err) - - if cnt != 1 { - err = fmt.Errorf("insert not returned 1 but %d", cnt) - t.Error(err) - panic(err) - } - - if a.Id == 0 { - err = errors.New("insert returned id is 0") - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, cnt) + assert.Greater(t, a.Id, int32(0)) cnt, err = testEngine.ID(a.Id).Update(&Article{Name: "6"}) assert.NoError(t, err) @@ -807,44 +677,20 @@ func TestUpdateSameMapper(t *testing.T) { { col1 := &UpdateIncr{} err = testEngine.Sync(col1) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) _, err = testEngine.Insert(col1) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) cnt, err := testEngine.ID(col1.Id).Incr("`Cnt`").Update(col1) - if err != nil { - t.Error(err) - panic(err) - } - if cnt != 1 { - err = errors.New("update incr failed") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) newCol := new(UpdateIncr) has, err := testEngine.ID(col1.Id).Get(newCol) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - err = errors.New("has incr failed") - t.Error(err) - panic(err) - } - if 1 != newCol.Cnt { - err = errors.New("incr failed") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, newCol.Cnt) } } @@ -853,17 +699,11 @@ func TestUseBool(t *testing.T) { assertSync(t, new(Userinfo)) cnt1, err := testEngine.Count(&Userinfo{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) users := make([]Userinfo, 0) err = testEngine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) var fNumber int64 for _, u := range users { if u.IsMan == false { @@ -872,10 +712,7 @@ func TestUseBool(t *testing.T) { } cnt2, err := testEngine.UseBool().Update(&Userinfo{IsMan: true}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) if fNumber != cnt2 { fmt.Println("cnt1", cnt1, "fNumber", fNumber, "cnt2", cnt2) /*err = errors.New("Updated number is not corrected.") @@ -884,11 +721,7 @@ func TestUseBool(t *testing.T) { } _, err = testEngine.Update(&Userinfo{IsMan: true}) - if err == nil { - err = errors.New("error condition") - t.Error(err) - panic(err) - } + assert.Error(t, err) } func TestBool(t *testing.T) { @@ -896,41 +729,21 @@ func TestBool(t *testing.T) { assertSync(t, new(Userinfo)) _, err := testEngine.UseBool().Update(&Userinfo{IsMan: true}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) users := make([]Userinfo, 0) err = testEngine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) for _, user := range users { - if !user.IsMan { - err = errors.New("update bool or find bool error") - t.Error(err) - panic(err) - } + assert.True(t, user.IsMan) } _, err = testEngine.UseBool().Update(&Userinfo{IsMan: false}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) users = make([]Userinfo, 0) err = testEngine.Find(&users) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) for _, user := range users { - if user.IsMan { - err = errors.New("update bool or find bool error") - t.Error(err) - panic(err) - } + assert.True(t, user.IsMan) } } @@ -1452,3 +1265,41 @@ func TestUpdateMap3(t *testing.T) { assert.Error(t, err) assert.EqualValues(t, 0, rows) } + +func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { + type TestOnlyFromDBField struct { + Id int64 `xorm:"PK"` + OnlyFromDBField string `xorm:"<-"` + OnlyToDBField string `xorm:"->"` + IngoreField string `xorm:"-"` + } + + assertGetRecord := func() *TestOnlyFromDBField { + var record TestOnlyFromDBField + has, err := testEngine.Where("id = ?", 1).Get(&record) + assert.NoError(t, err) + assert.EqualValues(t, true, has) + assert.EqualValues(t, "", record.OnlyFromDBField) + return &record + + } + assert.NoError(t, prepareEngine()) + assertSync(t, new(TestOnlyFromDBField)) + + _, err := testEngine.Insert(&TestOnlyFromDBField{ + Id: 1, + OnlyFromDBField: "a", + OnlyToDBField: "b", + IngoreField: "c", + }) + assert.NoError(t, err) + + assertGetRecord() + + _, err = testEngine.ID(1).Update(&TestOnlyFromDBField{ + OnlyToDBField: "b", + OnlyFromDBField: "test", + }) + assert.NoError(t, err) + assertGetRecord() +} diff --git a/statement_test.go b/statement_test.go deleted file mode 100644 index 57d6e477..00000000 --- a/statement_test.go +++ /dev/null @@ -1,71 +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 ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDistinctAndCols(t *testing.T) { - type DistinctAndCols struct { - Id int64 - Name string - } - - assert.NoError(t, prepareEngine()) - assertSync(t, new(DistinctAndCols)) - - cnt, err := testEngine.Insert(&DistinctAndCols{ - Name: "test", - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - - var names []string - err = testEngine.Table("distinct_and_cols").Cols("name").Distinct("name").Find(&names) - assert.NoError(t, err) - assert.EqualValues(t, 1, len(names)) - assert.EqualValues(t, "test", names[0]) -} - -func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { - type TestOnlyFromDBField struct { - Id int64 `xorm:"PK"` - OnlyFromDBField string `xorm:"<-"` - OnlyToDBField string `xorm:"->"` - IngoreField string `xorm:"-"` - } - - assertGetRecord := func() *TestOnlyFromDBField { - var record TestOnlyFromDBField - has, err := testEngine.Where("id = ?", 1).Get(&record) - assert.NoError(t, err) - assert.EqualValues(t, true, has) - assert.EqualValues(t, "", record.OnlyFromDBField) - return &record - - } - assert.NoError(t, prepareEngine()) - assertSync(t, new(TestOnlyFromDBField)) - - _, err := testEngine.Insert(&TestOnlyFromDBField{ - Id: 1, - OnlyFromDBField: "a", - OnlyToDBField: "b", - IngoreField: "c", - }) - assert.NoError(t, err) - - assertGetRecord() - - _, err = testEngine.ID(1).Update(&TestOnlyFromDBField{ - OnlyToDBField: "b", - OnlyFromDBField: "test", - }) - assert.NoError(t, err) - assertGetRecord() -} diff --git a/tags_test.go b/tags_test.go index 295affd8..4473a12f 100644 --- a/tags_test.go +++ b/tags_test.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "strings" "testing" @@ -275,25 +274,16 @@ func TestExtends3(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) var sender = MessageUser{Name: "sender"} var receiver = MessageUser{Name: "receiver"} var msgtype = MessageType{Name: "type"} _, err = testEngine.Insert(&sender, &receiver, &msgtype) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) msg := Message{ MessageBase: MessageBase{ @@ -335,60 +325,29 @@ func TestExtends3(t *testing.T) { Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). Find(&list) assert.NoError(t, err) - - if len(list) != 1 { - err = errors.New(fmt.Sprintln("should have 1 message, got", len(list))) - t.Error(err) - panic(err) - } - - if list[0].Message.Id != msg.Id { - err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg)) - t.Error(err) - panic(err) - } - - if list[0].Sender.Id != sender.Id || list[0].Sender.Name != sender.Name { - err = errors.New(fmt.Sprintln("should sender equal", list[0].Sender, sender)) - t.Error(err) - panic(err) - } - - if list[0].Receiver.Id != receiver.Id || list[0].Receiver.Name != receiver.Name { - err = errors.New(fmt.Sprintln("should receiver equal", list[0].Receiver, receiver)) - t.Error(err) - panic(err) - } - - if list[0].Type.Id != msgtype.Id || list[0].Type.Name != msgtype.Name { - err = errors.New(fmt.Sprintln("should msgtype equal", list[0].Type, msgtype)) - t.Error(err) - panic(err) - } + assert.EqualValues(t, 1, len(list)) + assert.EqualValues(t, list[0].Message.Id, msg.Id) + assert.EqualValues(t, list[0].Sender.Id, sender.Id) + assert.EqualValues(t, list[0].Sender.Name, sender.Name) + assert.EqualValues(t, list[0].Receiver.Id, receiver.Id) + assert.EqualValues(t, list[0].Receiver.Name, receiver.Name) + assert.EqualValues(t, list[0].Type.Id, msgtype.Id) + assert.EqualValues(t, list[0].Type.Name, msgtype.Name) } func TestExtends4(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) var sender = MessageUser{Name: "sender"} var msgtype = MessageType{Name: "type"} _, err = testEngine.Insert(&sender, &msgtype) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) msg := Message{ MessageBase: MessageBase{ @@ -427,34 +386,13 @@ func TestExtends4(t *testing.T) { err = session.Table(msgTableName).Join("LEFT", userTableName, userTableName+".`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Uid")+"`"). Join("LEFT", typeTableName, typeTableName+".`"+mapper("Id")+"`="+msgTableName+".`"+mapper("Id")+"`"). Find(&list) - if err != nil { - t.Error(err) - panic(err) - } - - if len(list) != 1 { - err = errors.New(fmt.Sprintln("should have 1 message, got", len(list))) - t.Error(err) - panic(err) - } - - if list[0].Message.Id != msg.Id { - err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg)) - t.Error(err) - panic(err) - } - - if list[0].MessageUser.Id != sender.Id || list[0].MessageUser.Name != sender.Name { - err = errors.New(fmt.Sprintln("should sender equal", list[0].MessageUser, sender)) - t.Error(err) - panic(err) - } - - if list[0].MessageType.Id != msgtype.Id || list[0].MessageType.Name != msgtype.Name { - err = errors.New(fmt.Sprintln("should msgtype equal", list[0].MessageType, msgtype)) - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, len(list), 1) + assert.EqualValues(t, list[0].Message.Id, msg.Id) + assert.EqualValues(t, list[0].MessageUser.Id, sender.Id) + assert.EqualValues(t, list[0].MessageUser.Name, sender.Name) + assert.EqualValues(t, list[0].MessageType.Id, msgtype.Id) + assert.EqualValues(t, list[0].MessageType.Name, msgtype.Name) } type Size struct { @@ -473,16 +411,10 @@ type Book struct { func TestExtends5(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(&Book{}, &Size{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(&Size{}, &Book{}) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) var sc = Size{Width: 0.2, Height: 0.4} var so = Size{Width: 0.2, Height: 0.8} @@ -547,10 +479,7 @@ func TestExtends5(t *testing.T) { bookTableName+".`Size`=s.`id`", ). Find(&list) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) for _, book := range list { if ok := assert.Equal(t, books[book.ID].SizeClosed.Width, book.SizeClosed.Width); !ok { @@ -1262,106 +1191,50 @@ func TestVersion1(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) ver := &VersionS{Name: "sfsfdsfds"} _, err = testEngine.Insert(ver) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(ver) - if ver.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, ver.Ver, 1) newVer := new(VersionS) has, err := testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - t.Error(fmt.Errorf("no version id is %v", ver.Id)) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, newVer.Ver, 1) newVer.Name = "-------" _, err = testEngine.ID(ver.Id).Update(newVer) - if err != nil { - t.Error(err) - panic(err) - } - if newVer.Ver != 2 { - err = errors.New("update should set version back to struct") - t.Error(err) - } + assert.NoError(t, err) + assert.EqualValues(t, newVer.Ver, 2) newVer = new(VersionS) has, err = testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 2 { - err = errors.New("update error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, newVer.Ver, 2) } func TestVersion2(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(new(VersionS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) var vers = []VersionS{ {Name: "sfsfdsfds"}, {Name: "xxxxx"}, } _, err = testEngine.Insert(vers) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(vers) - + assert.NoError(t, err) for _, v := range vers { - if v.Ver != 1 { - err := errors.New("version should be 1") - t.Error(err) - panic(err) - } + assert.EqualValues(t, v.Ver, 1) } } @@ -1376,105 +1249,49 @@ func TestVersion3(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) ver := &VersionUintS{Name: "sfsfdsfds"} _, err = testEngine.Insert(ver) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(ver) - if ver.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, ver.Ver, 1) newVer := new(VersionUintS) has, err := testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - - if !has { - t.Error(fmt.Errorf("no version id is %v", ver.Id)) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, newVer.Ver, 1) newVer.Name = "-------" _, err = testEngine.ID(ver.Id).Update(newVer) - if err != nil { - t.Error(err) - panic(err) - } - if newVer.Ver != 2 { - err = errors.New("update should set version back to struct") - t.Error(err) - } + assert.NoError(t, err) + assert.EqualValues(t, newVer.Ver, 2) newVer = new(VersionUintS) has, err = testEngine.ID(ver.Id).Get(newVer) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(newVer) - if newVer.Ver != 2 { - err = errors.New("update error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, newVer.Ver, 2) } func TestVersion4(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) err = testEngine.CreateTables(new(VersionUintS)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) var vers = []VersionUintS{ {Name: "sfsfdsfds"}, {Name: "xxxxx"}, } _, err = testEngine.Insert(vers) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(vers) - + assert.NoError(t, err) for _, v := range vers { - if v.Ver != 1 { - err := errors.New("version should be 1") - t.Error(err) - panic(err) - } + assert.EqualValues(t, v.Ver, 1) } } diff --git a/time_test.go b/time_test.go index 44a84553..6a60f03c 100644 --- a/time_test.go +++ b/time_test.go @@ -10,10 +10,15 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "xorm.io/xorm/internal/utils" + + "github.com/stretchr/testify/assert" ) +func formatTime(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + func TestTimeUserTime(t *testing.T) { assert.NoError(t, prepareEngine()) diff --git a/transaction.go b/transaction.go deleted file mode 100644 index 4104103f..00000000 --- a/transaction.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 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 - -// Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred -func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) { - session := engine.NewSession() - defer session.Close() - - if err := session.Begin(); err != nil { - return nil, err - } - - result, err := f(session) - if err != nil { - return nil, err - } - - if err := session.Commit(); err != nil { - return nil, err - } - - return result, nil -} diff --git a/types_null_test.go b/types_null_test.go index 7a13837e..1d5d005e 100644 --- a/types_null_test.go +++ b/types_null_test.go @@ -60,20 +60,14 @@ func TestCreateNullStructTable(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.CreateTables(new(NullType)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } func TestDropNullStructTable(t *testing.T) { assert.NoError(t, prepareEngine()) err := testEngine.DropTables(new(NullType)) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } func TestNullStructInsert(t *testing.T) { @@ -83,16 +77,8 @@ func TestNullStructInsert(t *testing.T) { if true { item := new(NullType) _, err := testEngine.Insert(item) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(item) - if item.Id != 1 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, item.Id, 1) } if true { @@ -103,16 +89,8 @@ func TestNullStructInsert(t *testing.T) { IsMan: sql.NullBool{Bool: true, Valid: true}, } _, err := testEngine.Insert(&item) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(item) - if item.Id != 2 { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, item.Id, 2) } if true { @@ -131,11 +109,7 @@ func TestNullStructInsert(t *testing.T) { } _, err := testEngine.Insert(&items) - if err != nil { - t.Error(err) - panic(err) - } - fmt.Println(items) + assert.NoError(t, err) } } @@ -177,30 +151,16 @@ func TestNullStructUpdate(t *testing.T) { item.Height = sql.NullFloat64{Float64: 0, Valid: false} // update to NULL affected, err := testEngine.ID(2).Cols("age", "height", "is_man").Update(item) - if err != nil { - t.Error(err) - panic(err) - } - if affected != 1 { - err := errors.New("update failed") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, affected, 1) } if true { // 测试In update item := new(NullType) item.Age = sql.NullInt64{Int64: 23, Valid: true} affected, err := testEngine.In("id", 3, 4).Cols("age", "height", "is_man").Update(item) - if err != nil { - t.Error(err) - panic(err) - } - if affected != 2 { - err := errors.New("update failed") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.EqualValues(t, affected, 2) } if true { // 测试where @@ -210,10 +170,7 @@ func TestNullStructUpdate(t *testing.T) { item.Age = sql.NullInt64{Int64: 34, Valid: true} _, err := testEngine.Where("age > ?", 34).Update(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } if true { // 修改全部时,插入空值 @@ -225,10 +182,7 @@ func TestNullStructUpdate(t *testing.T) { } _, err := testEngine.AllCols().ID(6).Update(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) fmt.Println(item) } @@ -269,63 +223,33 @@ func TestNullStructFind(t *testing.T) { if true { item := new(NullType) has, err := testEngine.ID(1).Get(item) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - t.Error(errors.New("no find id 1")) - panic(err) - } - fmt.Println(item) - if item.Id != 1 || item.Name.Valid || item.Age.Valid || item.Height.Valid || - item.IsMan.Valid { - err = errors.New("insert error") - t.Error(err) - panic(err) - } + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, item.Id, 1) + assert.False(t, item.Name.Valid) + assert.False(t, item.Age.Valid) + assert.False(t, item.Height.Valid) + assert.False(t, item.IsMan.Valid) } if true { item := new(NullType) item.Id = 2 - has, err := testEngine.Get(item) - if err != nil { - t.Error(err) - panic(err) - } - if !has { - t.Error(errors.New("no find id 2")) - panic(err) - } - fmt.Println(item) + assert.NoError(t, err) + assert.True(t, has) } if true { item := make([]NullType, 0) - err := testEngine.ID(2).Find(&item) - if err != nil { - t.Error(err) - panic(err) - } - - fmt.Println(item) + assert.NoError(t, err) } if true { item := make([]NullType, 0) - err := testEngine.Asc("age").Find(&item) - if err != nil { - t.Error(err) - panic(err) - } - - for k, v := range item { - fmt.Println(k, v) - } + assert.NoError(t, err) } } @@ -340,10 +264,7 @@ func TestNullStructIterate(t *testing.T) { fmt.Println(i, nultype) return nil }) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } } @@ -354,10 +275,7 @@ func TestNullStructCount(t *testing.T) { if true { item := new(NullType) total, err := testEngine.Where("age IS NOT NULL").Count(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) fmt.Println(total) } } @@ -368,18 +286,12 @@ func TestNullStructRows(t *testing.T) { item := new(NullType) rows, err := testEngine.Where("id > ?", 1).Rows(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) defer rows.Close() for rows.Next() { err = rows.Scan(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) fmt.Println(item) } } @@ -391,14 +303,8 @@ func TestNullStructDelete(t *testing.T) { item := new(NullType) _, err := testEngine.ID(1).Delete(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) _, err = testEngine.Where("id > ?", 1).Delete(item) - if err != nil { - t.Error(err) - panic(err) - } + assert.NoError(t, err) } From f51d28304a2d89edfb5a57b42d91080a79cfa6c7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 6 Mar 2020 06:43:49 +0000 Subject: [PATCH 051/112] Move some codes to statement sub package (#1574) revert change for delete refactor new engine fix tests Move some codes to statement sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1574 --- .drone.yml | 4 +- dialects/dialect.go | 28 ++----- dialects/driver.go | 32 +++++++ dialects/mssql.go | 4 +- dialects/mysql.go | 4 +- dialects/oracle.go | 4 +- dialects/postgres.go | 13 ++- dialects/sqlite3.go | 4 +- engine.go | 29 ++++--- error.go | 4 - interface.go | 1 + internal/statements/query.go | 50 +++++------ internal/statements/statement_test.go | 116 ++++++++++++++------------ session.go | 2 +- session_convert.go | 3 +- session_delete.go | 8 ++ session_get_test.go | 2 +- session_tx.go | 4 +- xorm.go | 29 +------ 19 files changed, 169 insertions(+), 172 deletions(-) diff --git a/.drone.yml b/.drone.yml index dac49cdf..9a62c6bd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,8 +22,10 @@ steps: commands: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite - - go test ./caches/... ./convert/... ./core/... ./dialects/... \ + - go test ./caches/... ./contexts/... ./convert/... ./core/... ./dialects/... \ + ./internal/json/... ./internal/statements/... ./internal/utils/... \ ./log/... ./migrate/... ./names/... ./schemas/... ./tags/... + when: event: - push diff --git a/dialects/dialect.go b/dialects/dialect.go index a0139d9f..c591cc7b 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -31,7 +31,7 @@ type URI struct { // Dialect represents a kind of database type Dialect interface { - Init(*core.DB, *URI, string, string) error + Init(*core.DB, *URI) error URI() *URI DB() *core.DB DBType() schemas.DBType @@ -39,9 +39,6 @@ type Dialect interface { FormatBytes(b []byte) string DefaultSchema() string - DriverName() string - DataSourceName() string - IsReserved(string) bool Quoter() schemas.Quoter @@ -77,17 +74,11 @@ type Dialect interface { SetParams(params map[string]string) } -func OpenDialect(dialect Dialect) (*core.DB, error) { - return core.Open(dialect.DriverName(), dialect.DataSourceName()) -} - // Base represents a basic dialect and all real dialects could embed this struct type Base struct { - db *core.DB - dialect Dialect - driverName string - dataSourceName string - uri *URI + db *core.DB + dialect Dialect + uri *URI } func (b *Base) DB() *core.DB { @@ -98,9 +89,8 @@ func (b *Base) DefaultSchema() string { return "" } -func (b *Base) Init(db *core.DB, dialect Dialect, uri *URI, drivername, dataSourceName string) error { +func (b *Base) Init(db *core.DB, dialect Dialect, uri *URI) error { b.db, b.dialect, b.uri = db, dialect, uri - b.driverName, b.dataSourceName = drivername, dataSourceName return nil } @@ -165,18 +155,10 @@ func (b *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } -func (b *Base) DriverName() string { - return b.driverName -} - func (b *Base) ShowCreateNull() bool { return true } -func (b *Base) DataSourceName() string { - return b.dataSourceName -} - func (db *Base) SupportDropIfExists() bool { return true } diff --git a/dialects/driver.go b/dialects/driver.go index 5343d594..89d21bfc 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -4,6 +4,12 @@ package dialects +import ( + "fmt" + + "xorm.io/xorm/core" +) + type Driver interface { Parse(string, string) (*URI, error) } @@ -29,3 +35,29 @@ func QueryDriver(driverName string) Driver { func RegisteredDriverSize() int { return len(drivers) } + +// OpenDialect opens a dialect via driver name and connection string +func OpenDialect(driverName, connstr string) (Dialect, error) { + driver := QueryDriver(driverName) + if driver == nil { + return nil, fmt.Errorf("Unsupported driver name: %v", driverName) + } + + uri, err := driver.Parse(driverName, connstr) + if err != nil { + return nil, err + } + + dialect := QueryDialect(uri.DBType) + if dialect == nil { + return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DBType) + } + + db, err := core.Open(driverName, connstr) + if err != nil { + return nil, err + } + dialect.Init(db, uri) + + return dialect, nil +} diff --git a/dialects/mssql.go b/dialects/mssql.go index 9963fc4f..558abdfc 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -210,8 +210,8 @@ type mssql struct { Base } -func (db *mssql) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { - return db.Base.Init(d, db, uri, drivername, dataSourceName) +func (db *mssql) Init(d *core.DB, uri *URI) error { + return db.Base.Init(d, db, uri) } func (db *mssql) SQLType(c *schemas.Column) string { diff --git a/dialects/mysql.go b/dialects/mysql.go index 5ed2d8f1..939a7cf1 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -177,8 +177,8 @@ type mysql struct { rowFormat string } -func (db *mysql) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { - return db.Base.Init(d, db, uri, drivername, dataSourceName) +func (db *mysql) Init(d *core.DB, uri *URI) error { + return db.Base.Init(d, db, uri) } func (db *mysql) SetParams(params map[string]string) { diff --git a/dialects/oracle.go b/dialects/oracle.go index e5c438bc..4a8162ac 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -504,8 +504,8 @@ type oracle struct { Base } -func (db *oracle) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { - return db.Base.Init(d, db, uri, drivername, dataSourceName) +func (db *oracle) Init(d *core.DB, uri *URI) error { + return db.Base.Init(d, db, uri) } func (db *oracle) SQLType(c *schemas.Column) string { diff --git a/dialects/postgres.go b/dialects/postgres.go index 623b59ed..f92202cd 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -766,30 +766,27 @@ var ( "YES": true, "ZONE": true, } - - // DefaultPostgresSchema default postgres schema - DefaultPostgresSchema = "public" ) -const PostgresPublicSchema = "public" +const postgresPublicSchema = "public" type postgres struct { Base } -func (db *postgres) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { - err := db.Base.Init(d, db, uri, drivername, dataSourceName) +func (db *postgres) Init(d *core.DB, uri *URI) error { + err := db.Base.Init(d, db, uri) if err != nil { return err } if db.uri.Schema == "" { - db.uri.Schema = DefaultPostgresSchema + db.uri.Schema = postgresPublicSchema } return nil } func (db *postgres) DefaultSchema() string { - return PostgresPublicSchema + return postgresPublicSchema } func (db *postgres) SQLType(c *schemas.Column) string { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 7dfa7fca..39138b13 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -149,8 +149,8 @@ type sqlite3 struct { Base } -func (db *sqlite3) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { - return db.Base.Init(d, db, uri, drivername, dataSourceName) +func (db *sqlite3) Init(d *core.DB, uri *URI) error { + return db.Base.Init(d, db, uri) } func (db *sqlite3) SQLType(c *schemas.Column) string { diff --git a/engine.go b/engine.go index 221b7488..cc8a74a0 100644 --- a/engine.go +++ b/engine.go @@ -39,6 +39,9 @@ type Engine struct { logger log.ContextLogger tagParser *tags.Parser + driverName string + dataSourceName string + TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database } @@ -61,7 +64,7 @@ func (engine *Engine) BufferSize(size int) *Session { // ShowSQL show SQL statement or not on logger if log level is great than INFO func (engine *Engine) ShowSQL(show ...bool) { engine.logger.ShowSQL(show...) - engine.db.Logger = engine.logger + engine.DB().Logger = engine.logger } // Logger return the logger interface @@ -79,7 +82,7 @@ func (engine *Engine) SetLogger(logger interface{}) { realLogger = t } engine.logger = realLogger - engine.db.Logger = realLogger + engine.DB().Logger = realLogger } // SetLogLevel sets the logger level @@ -94,12 +97,12 @@ func (engine *Engine) SetDisableGlobalCache(disable bool) { // DriverName return the current sql driver's name func (engine *Engine) DriverName() string { - return engine.dialect.DriverName() + return engine.driverName } // DataSourceName return the current connection string func (engine *Engine) DataSourceName() string { - return engine.dialect.DataSourceName() + return engine.dataSourceName } // SetMapper set the name mapping rules @@ -164,17 +167,17 @@ func (engine *Engine) AutoIncrStr() string { // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. func (engine *Engine) SetConnMaxLifetime(d time.Duration) { - engine.db.SetConnMaxLifetime(d) + engine.DB().SetConnMaxLifetime(d) } // SetMaxOpenConns is only available for go 1.2+ func (engine *Engine) SetMaxOpenConns(conns int) { - engine.db.SetMaxOpenConns(conns) + engine.DB().SetMaxOpenConns(conns) } // SetMaxIdleConns set the max idle connections on pool, default is 2 func (engine *Engine) SetMaxIdleConns(conns int) { - engine.db.SetMaxIdleConns(conns) + engine.DB().SetMaxIdleConns(conns) } // SetDefaultCacher set the default cacher. Xorm's default not enable cacher. @@ -210,12 +213,12 @@ func (engine *Engine) MapCacher(bean interface{}, cacher caches.Cacher) error { // NewDB provides an interface to operate database directly func (engine *Engine) NewDB() (*core.DB, error) { - return dialects.OpenDialect(engine.dialect) + return core.Open(engine.driverName, engine.dataSourceName) } // DB return the wrapper of sql.DB func (engine *Engine) DB() *core.DB { - return engine.db + return engine.dialect.DB() } // Dialect return database dialect @@ -232,7 +235,7 @@ func (engine *Engine) NewSession() *Session { // Close the engine func (engine *Engine) Close() error { - return engine.db.Close() + return engine.DB().Close() } // Ping tests if database is alive @@ -364,7 +367,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch if dialect == nil { return errors.New("Unsupported database type") } - dialect.Init(nil, engine.dialect.URI(), "", "") + dialect.Init(nil, engine.dialect.URI()) distDBName = string(tp[0]) } @@ -1211,10 +1214,6 @@ func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) { return dialects.FormatTime(engine.dialect, col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) } -func (engine *Engine) formatColTime(col *schemas.Column, t time.Time) (v interface{}) { - return dialects.FormatColumnTime(engine.dialect, engine.DatabaseTZ, col, t) -} - // GetColumnMapper returns the column name mapper func (engine *Engine) GetColumnMapper() names.Mapper { return engine.tagParser.GetColumnMapper() diff --git a/error.go b/error.go index a19860e3..21a83f47 100644 --- a/error.go +++ b/error.go @@ -20,10 +20,6 @@ var ( ErrNotExist = errors.New("Record does not exist") // ErrCacheFailed cache failed error ErrCacheFailed = errors.New("Cache failed") - // ErrNeedDeletedCond delete needs less one condition error - ErrNeedDeletedCond = errors.New("Delete action needs at least one condition") - // ErrNotImplemented not implemented - ErrNotImplemented = errors.New("Not implemented") // ErrConditionType condition type unsupported ErrConditionType = errors.New("Unsupported condition type") ) diff --git a/interface.go b/interface.go index 13f1e12a..8d2402f0 100644 --- a/interface.go +++ b/interface.go @@ -82,6 +82,7 @@ type EngineInterface interface { CreateTables(...interface{}) error DBMetas() ([]*schemas.Table, error) Dialect() dialects.Dialect + DriverName() string DropTables(...interface{}) error DumpAllToFile(fp string, tp ...schemas.DBType) error GetCacher(string) caches.Cacher diff --git a/internal/statements/query.go b/internal/statements/query.go index 1519cb08..a058f752 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -57,16 +57,12 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return "", nil, err } - condSQL, condArgs, err := builder.ToSQL(statement.cond) + sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) if err != nil { return "", nil, err } - args := append(statement.joinArgs, condArgs...) - sqlStr, err := statement.GenSelectSQL(columnStr, condSQL, true, true) - if err != nil { - return "", nil, err - } + // for mssql and use limit qs := strings.Count(sqlStr, "?") if len(args)*2 == qs { @@ -92,12 +88,11 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri } sumSelect := strings.Join(sumStrs, ", ") - condSQL, condArgs, err := statement.GenConds(bean) - if err != nil { + if err := statement.mergeConds(bean); err != nil { return "", nil, err } - sqlStr, err := statement.GenSelectSQL(sumSelect, condSQL, true, true) + sqlStr, condArgs, err := statement.genSelectSQL(sumSelect, true, true) if err != nil { return "", nil, err } @@ -147,12 +142,8 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, return "", nil, err } } - condSQL, condArgs, err := builder.ToSQL(statement.cond) - if err != nil { - return "", nil, err - } - sqlStr, err := statement.GenSelectSQL(columnStr, condSQL, true, true) + sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) if err != nil { return "", nil, err } @@ -165,17 +156,13 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa return statement.RawSQL, statement.RawParams, nil } - var condSQL string var condArgs []interface{} var err error if len(beans) > 0 { statement.SetRefBean(beans[0]) - condSQL, condArgs, err = statement.GenConds(beans[0]) - } else { - condSQL, condArgs, err = builder.ToSQL(statement.cond) - } - if err != nil { - return "", nil, err + if err := statement.mergeConds(beans[0]); err != nil { + return "", nil, err + } } var selectSQL = statement.SelectStr @@ -186,7 +173,7 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa selectSQL = "count(*)" } } - sqlStr, err := statement.GenSelectSQL(selectSQL, condSQL, false, false) + sqlStr, condArgs, err := statement.genSelectSQL(selectSQL, false, false) if err != nil { return "", nil, err } @@ -194,7 +181,7 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa return sqlStr, append(statement.joinArgs, condArgs...), nil } -func (statement *Statement) GenSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) { +func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderBy bool) (string, []interface{}, error) { var ( distinct string dialect = statement.dialect @@ -205,6 +192,11 @@ func (statement *Statement) GenSelectSQL(columnStr, condSQL string, needLimit, n if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { distinct = "DISTINCT " } + + condSQL, condArgs, err := builder.ToSQL(statement.cond) + if err != nil { + return "", nil, err + } if len(condSQL) > 0 { whereStr = " WHERE " + condSQL } @@ -313,10 +305,10 @@ func (statement *Statement) GenSelectSQL(columnStr, condSQL string, needLimit, n } } if statement.IsForUpdate { - return dialect.ForUpdateSQL(buf.String()), nil + return dialect.ForUpdateSQL(buf.String()), condArgs, nil } - return buf.String(), nil + return buf.String(), condArgs, nil } func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interface{}, error) { @@ -428,16 +420,12 @@ func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interfa } statement.cond = statement.cond.And(autoCond) - condSQL, condArgs, err := builder.ToSQL(statement.cond) - if err != nil { - return "", nil, err - } - args = append(statement.joinArgs, condArgs...) - sqlStr, err = statement.GenSelectSQL(columnStr, condSQL, true, true) + 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 { diff --git a/internal/statements/statement_test.go b/internal/statements/statement_test.go index 3b6e3ae2..15f446f4 100644 --- a/internal/statements/statement_test.go +++ b/internal/statements/statement_test.go @@ -8,10 +8,37 @@ 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" + "xorm.io/xorm/tags" + + _ "github.com/mattn/go-sqlite3" ) +var ( + dialect dialects.Dialect + tagParser *tags.Parser +) + +func TestMain(m *testing.M) { + var err error + dialect, err = dialects.OpenDialect("sqlite3", "./test.db") + if err != nil { + panic("unknow dialect") + } + + tagParser = tags.NewParser("xorm", dialect, names.SnakeMapper{}, names.SnakeMapper{}, caches.NewManager()) + if tagParser == nil { + panic("tags parser is nil") + } + m.Run() +} + var colStrTests = []struct { omitColumn string onlyToDBColumnNdx int @@ -26,14 +53,9 @@ var colStrTests = []struct { } func TestColumnsStringGeneration(t *testing.T) { - if dbType == "postgres" || dbType == "mssql" { - return - } - - var statement *Statement - for ndx, testCase := range colStrTests { - statement = createTestStatement() + statement, err := createTestStatement() + assert.NoError(t, err) if testCase.omitColumn != "" { statement.Omit(testCase.omitColumn) @@ -55,33 +77,6 @@ func TestColumnsStringGeneration(t *testing.T) { } } -func BenchmarkColumnsStringGeneration(b *testing.B) { - b.StopTimer() - - statement := createTestStatement() - - testCase := colStrTests[0] - - if testCase.omitColumn != "" { - statement.Omit(testCase.omitColumn) // !nemec784! Column must be skipped - } - - if testCase.onlyToDBColumnNdx >= 0 { - columns := statement.RefTable.Columns() - columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB // !nemec784! Column must be skipped - } - - b.StartTimer() - - for i := 0; i < b.N; i++ { - actual := statement.genColumnStr() - - if actual != testCase.expected { - b.Errorf("Unexpected columns string:\nwant:\t%s\nhave:\t%s", testCase.expected, actual) - } - } -} - func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { b.StopTimer() @@ -162,23 +157,40 @@ func (TestType) TableName() string { return "TestTable" } -func createTestStatement() *Statement { - if engine, ok := testEngine.(*Engine); ok { - statement := &Statement{} - statement.Reset() - statement.Engine = engine - statement.dialect = engine.dialect - statement.SetRefValue(reflect.ValueOf(TestType{})) - - return statement - } else if eg, ok := testEngine.(*EngineGroup); ok { - statement := &Statement{} - statement.Reset() - statement.Engine = eg.Engine - statement.dialect = eg.Engine.dialect - statement.SetRefValue(reflect.ValueOf(TestType{})) - - return statement +func createTestStatement() (*Statement, error) { + statement := NewStatement(dialect, tagParser, time.Local) + if err := statement.SetRefValue(reflect.ValueOf(TestType{})); err != nil { + return nil, err + } + return statement, nil +} + +func BenchmarkColumnsStringGeneration(b *testing.B) { + b.StopTimer() + + statement, err := createTestStatement() + if err != nil { + panic(err) + } + + testCase := colStrTests[0] + + if testCase.omitColumn != "" { + statement.Omit(testCase.omitColumn) // !nemec784! Column must be skipped + } + + if testCase.onlyToDBColumnNdx >= 0 { + columns := statement.RefTable.Columns() + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB // !nemec784! Column must be skipped + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + actual := statement.genColumnStr() + + if actual != testCase.expected { + b.Errorf("Unexpected columns string:\nwant:\t%s\nhave:\t%s", testCase.expected, actual) + } } - return nil } diff --git a/session.go b/session.go index db990684..07b99594 100644 --- a/session.go +++ b/session.go @@ -284,7 +284,7 @@ func (session *Session) Having(conditions string) *Session { // DB db return the wrapper of sql.DB func (session *Session) DB() *core.DB { if session.db == nil { - session.db = session.engine.db + session.db = session.engine.DB() session.stmtCache = make(map[uint32]*core.Stmt, 0) } return session.db diff --git a/session_convert.go b/session_convert.go index 1cd00627..0776bc45 100644 --- a/session_convert.go +++ b/session_convert.go @@ -15,6 +15,7 @@ import ( "time" "xorm.io/xorm/convert" + "xorm.io/xorm/dialects" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" @@ -583,7 +584,7 @@ func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect. case reflect.Struct: if fieldType.ConvertibleTo(schemas.TimeType) { t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) - tf := session.engine.formatColTime(col, t) + tf := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, t) return tf, nil } else if fieldType.ConvertibleTo(nullFloatType) { t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64) diff --git a/session_delete.go b/session_delete.go index 04200035..eb5e2aea 100644 --- a/session_delete.go +++ b/session_delete.go @@ -13,6 +13,14 @@ import ( "xorm.io/xorm/schemas" ) +var ( + // ErrNeedDeletedCond delete needs less one condition error + ErrNeedDeletedCond = errors.New("Delete action needs at least one condition") + + // ErrNotImplemented not implemented + ErrNotImplemented = errors.New("Not implemented") +) + func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { diff --git a/session_get_test.go b/session_get_test.go index 5bac9cd7..7e10bf54 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -179,7 +179,7 @@ func TestGetVar(t *testing.T) { assert.Equal(t, "1.5", valuesString["money"]) // for mymysql driver, interface{} will be []byte, so ignore it currently - if testEngine.Dialect().DriverName() != "mymysql" { + if testEngine.DriverName() != "mymysql" { var valuesInter = make(map[string]interface{}) has, err = testEngine.Table("get_var").Where("id = ?", 1).Select("*").Get(&valuesInter) assert.NoError(t, err) diff --git a/session_tx.go b/session_tx.go index 489489f3..cd23cf89 100644 --- a/session_tx.go +++ b/session_tx.go @@ -34,7 +34,7 @@ func (session *Session) Rollback() error { session.isAutoCommit = true start := time.Now() - needSQL := session.engine.db.NeedLogSQL(session.ctx) + needSQL := session.DB().NeedLogSQL(session.ctx) if needSQL { session.engine.logger.BeforeSQL(log.LogContext{ Ctx: session.ctx, @@ -63,7 +63,7 @@ func (session *Session) Commit() error { session.isAutoCommit = true start := time.Now() - needSQL := session.engine.db.NeedLogSQL(session.ctx) + needSQL := session.DB().NeedLogSQL(session.ctx) if needSQL { session.engine.logger.BeforeSQL(log.LogContext{ Ctx: session.ctx, diff --git a/xorm.go b/xorm.go index 724a37cb..3618b718 100644 --- a/xorm.go +++ b/xorm.go @@ -8,13 +8,11 @@ package xorm import ( "context" - "fmt" "os" "runtime" "time" "xorm.io/xorm/caches" - "xorm.io/xorm/core" "xorm.io/xorm/dialects" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -34,27 +32,7 @@ func close(engine *Engine) { // NewEngine new a db manager according to the parameter. Currently support four // drivers func NewEngine(driverName string, dataSourceName string) (*Engine, error) { - driver := dialects.QueryDriver(driverName) - if driver == nil { - return nil, fmt.Errorf("Unsupported driver name: %v", driverName) - } - - uri, err := driver.Parse(driverName, dataSourceName) - if err != nil { - return nil, err - } - - dialect := dialects.QueryDialect(uri.DBType) - if dialect == nil { - return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DBType) - } - - db, err := core.Open(driverName, dataSourceName) - if err != nil { - return nil, err - } - - err = dialect.Init(db, uri, driverName, dataSourceName) + dialect, err := dialects.OpenDialect(driverName, dataSourceName) if err != nil { return nil, err } @@ -64,15 +42,16 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) engine := &Engine{ - db: db, dialect: dialect, TZLocation: time.Local, defaultContext: context.Background(), cacherMgr: cacherMgr, tagParser: tagParser, + driverName: driverName, + dataSourceName: dataSourceName, } - if uri.DBType == schemas.SQLITE { + if dialect.URI().DBType == schemas.SQLITE { engine.DatabaseTZ = time.UTC } else { engine.DatabaseTZ = time.Local From 74550148237ffaf8a9b540904233667ddd5bdbb6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 6 Mar 2020 07:48:32 +0000 Subject: [PATCH 052/112] Improve quote policy (#1567) names with upper charactor on postgres will need quotes Fix bug Add new quote parameter on tests Fix bug Fix tests Fix quotes fix test Improve quote policy Reviewed-on: https://gitea.com/xorm/xorm/pulls/1567 --- .drone.yml | 11 +- Makefile | 37 +++--- dialects/dialect.go | 10 +- dialects/filter.go | 38 ++++-- dialects/filter_test.go | 31 +++-- dialects/mssql.go | 22 +++- dialects/mysql.go | 28 ++++- dialects/oracle.go | 28 ++++- dialects/postgres.go | 38 +++++- dialects/quote.go | 15 +++ dialects/sqlite3.go | 44 +++++-- engine.go | 4 + engine_group.go | 8 ++ interface.go | 1 + schemas/quote.go | 269 +++++++++++++++++++--------------------- schemas/quote_test.go | 133 +++++++++++++++----- types_null_test.go | 19 +-- xorm_test.go | 10 ++ 18 files changed, 490 insertions(+), 256 deletions(-) create mode 100644 dialects/quote.go diff --git a/.drone.yml b/.drone.yml index 9a62c6bd..0863cce2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,9 +22,10 @@ steps: commands: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite + - TEST_QUOTE_POLICY=reserved make test-sqlite - go test ./caches/... ./contexts/... ./convert/... ./core/... ./dialects/... \ + ./log/... ./migrate/... ./names/... ./schemas/... ./tags/... \ ./internal/json/... ./internal/statements/... ./internal/utils/... \ - ./log/... ./migrate/... ./names/... ./schemas/... ./tags/... when: event: @@ -44,6 +45,7 @@ steps: commands: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql + - TEST_QUOTE_POLICY=reserved make test-mysql when: event: - push @@ -62,6 +64,7 @@ steps: commands: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql + - TEST_QUOTE_POLICY=reserved make test-mysql when: event: - push @@ -82,6 +85,7 @@ steps: commands: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql + - TEST_QUOTE_POLICY=reserved make test-mysql when: event: - push @@ -102,6 +106,7 @@ steps: commands: - make test-mymysql - TEST_CACHE_ENABLE=true make test-mymysql + - TEST_QUOTE_POLICY=reserved make test-mymysql when: event: - push @@ -120,6 +125,7 @@ steps: commands: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres + - TEST_QUOTE_POLICY=reserved make test-postgres when: event: - push @@ -141,6 +147,7 @@ steps: commands: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres + - TEST_QUOTE_POLICY=reserved make test-postgres when: event: - push @@ -159,6 +166,7 @@ steps: commands: - make test-mssql - TEST_CACHE_ENABLE=true make test-mssql + - TEST_QUOTE_POLICY=reserved make test-mssql when: event: - push @@ -177,6 +185,7 @@ steps: commands: - make test-tidb - TEST_CACHE_ENABLE=true make test-tidb + - TEST_QUOTE_POLICY=reserved make test-tidb when: event: - push diff --git a/Makefile b/Makefile index faad978f..4444ebd0 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ TEST_TIDB_USERNAME ?= root TEST_TIDB_PASSWORD ?= TEST_CACHE_ENABLE ?= false +TEST_QUOTE_POLICY ?= always .PHONY: all all: build @@ -135,73 +136,73 @@ test-cockroach\#%: go-check .PNONY: test-mssql test-mssql: go-check - $(GO) test -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ - -coverprofile=mssql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mssql\#% test-mssql\#%: go-check - $(GO) test -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ - -coverprofile=mssql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mymysql test-mymysql: go-check - $(GO) test -v -race -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -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_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mymysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mymysql\#% test-mymysql\#%: go-check - $(GO) test -v -race -run $* -db=mymysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -v -race -run $* -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_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mymysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mysql test-mysql: go-check - $(GO) test -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -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_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-mysql\#% test-mysql\#%: go-check - $(GO) test -v -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test -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)" \ - -coverprofile=mysql.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-postgres test-postgres: go-check $(GO) test -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" \ - -coverprofile=postgres.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-postgres\#% test-postgres\#%: go-check $(GO) test -v -race -run $* -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" \ - -coverprofile=postgres.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite test-sqlite: go-check $(GO) test -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ - -coverprofile=sqlite.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -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 -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ - -coverprofile=sqlite.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -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 -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)" \ - -coverprofile=tidb.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=tidb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-tidb\#% test-tidb\#%: go-check $(GO) test -v -race -run $* -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)" \ - -coverprofile=tidb.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + -quote=$(TEST_QUOTE_POLICY) -coverprofile=tidb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: vet vet: diff --git a/dialects/dialect.go b/dialects/dialect.go index c591cc7b..d89f1ebe 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -41,6 +41,7 @@ type Dialect interface { IsReserved(string) bool Quoter() schemas.Quoter + SetQuotePolicy(quotePolicy QuotePolicy) AutoIncrStr() string @@ -79,6 +80,11 @@ type Base struct { db *core.DB dialect Dialect uri *URI + quoter schemas.Quoter +} + +func (b *Base) Quoter() schemas.Quoter { + return b.quoter } func (b *Base) DB() *core.DB { @@ -210,7 +216,7 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { idxName = index.XName(tableName) return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, quoter.Quote(idxName), quoter.Quote(tableName), - quoter.Quote(strings.Join(index.Cols, quoter.ReverseQuote(",")))) + quoter.Join(index.Cols, ",")) } func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { @@ -258,7 +264,7 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(","))) + sql += quoter.Join(pkList, ",") sql += " ), " } diff --git a/dialects/filter.go b/dialects/filter.go index 0f9b4107..add8cc7d 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -26,20 +26,36 @@ func (s *QuoteFilter) Do(sql string) string { return sql } - prefix, suffix := s.quoter[0][0], s.quoter[1][0] - raw := []byte(sql) - for i, cnt := 0, 0; i < len(raw); i = i + 1 { - if raw[i] == '`' { - if cnt%2 == 0 { - raw[i] = prefix - } else { - raw[i] = suffix + var buf strings.Builder + buf.Grow(len(sql)) + + var beginSingleQuote bool + for i := 0; i < len(sql); i++ { + if !beginSingleQuote && sql[i] == '`' { + var j = i + 1 + for ; j < len(sql); j++ { + if sql[j] == '`' { + break + } } - cnt++ + word := sql[i+1 : j] + isReserved := s.quoter.IsReserved(word) + if isReserved { + buf.WriteByte(s.quoter.Prefix) + } + buf.WriteString(word) + if isReserved { + buf.WriteByte(s.quoter.Suffix) + } + i = j + } else { + if sql[i] == '\'' { + beginSingleQuote = !beginSingleQuote + } + buf.WriteByte(sql[i]) } } - return string(raw) - + return buf.String() } // SeqFilter filter SQL replace ?, ? ... to $1, $2 ... diff --git a/dialects/filter_test.go b/dialects/filter_test.go index ac110a69..e8395156 100644 --- a/dialects/filter_test.go +++ b/dialects/filter_test.go @@ -9,13 +9,30 @@ import ( ) func TestQuoteFilter_Do(t *testing.T) { - f := QuoteFilter{schemas.Quoter{"[", "]"}} - sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" - res := f.Do(sql) - assert.EqualValues(t, - "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", - res, - ) + f := QuoteFilter{schemas.Quoter{'[', ']', schemas.AlwaysReserve}} + var kases = []struct { + source string + expected string + }{ + { + "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?", + "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", + }, + { + "SELECT 'abc```test```''', `a` FROM b", + "SELECT 'abc```test```''', [a] FROM b", + }, + { + "UPDATE table SET `a` = ~ `a`, `b`='abc`'", + "UPDATE table SET [a] = ~ [a], [b]='abc`'", + }, + } + + for _, kase := range kases { + t.Run(kase.source, func(t *testing.T) { + assert.EqualValues(t, kase.expected, f.Do(kase.source)) + }) + } } func TestSeqFilter(t *testing.T) { diff --git a/dialects/mssql.go b/dialects/mssql.go index 558abdfc..a2cbb361 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -204,6 +204,8 @@ var ( "EXIT": true, "PROC": true, } + + mssqlQuoter = schemas.Quoter{'[', ']', schemas.AlwaysReserve} ) type mssql struct { @@ -211,6 +213,7 @@ type mssql struct { } func (db *mssql) Init(d *core.DB, uri *URI) error { + db.quoter = mssqlQuoter return db.Base.Init(d, db, uri) } @@ -283,12 +286,25 @@ func (db *mssql) SupportInsertMany() bool { } func (db *mssql) IsReserved(name string) bool { - _, ok := mssqlReservedWords[name] + _, ok := mssqlReservedWords[strings.ToUpper(name)] return ok } -func (db *mssql) Quoter() schemas.Quoter { - return schemas.Quoter{"[", "]"} +func (db *mssql) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + var q = mssqlQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + var q = mssqlQuoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = mssqlQuoter + } } func (db *mssql) SupportEngine() bool { diff --git a/dialects/mysql.go b/dialects/mysql.go index 939a7cf1..5f36ed31 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -161,6 +161,8 @@ var ( "YEAR_MONTH": true, "ZEROFILL": true, } + + mysqlQuoter = schemas.Quoter{'`', '`', schemas.AlwaysReserve} ) type mysql struct { @@ -178,6 +180,7 @@ type mysql struct { } func (db *mysql) Init(d *core.DB, uri *URI) error { + db.quoter = mysqlQuoter return db.Base.Init(d, db, uri) } @@ -272,14 +275,10 @@ func (db *mysql) SupportInsertMany() bool { } func (db *mysql) IsReserved(name string) bool { - _, ok := mysqlReservedWords[name] + _, ok := mysqlReservedWords[strings.ToUpper(name)] return ok } -func (db *mysql) Quoter() schemas.Quoter { - return schemas.Quoter{"`", "`"} -} - func (db *mysql) SupportEngine() bool { return true } @@ -458,6 +457,23 @@ func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { return tables, nil } +func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + var q = mysqlQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + var q = mysqlQuoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = mysqlQuoter + } +} + func (db *mysql) GetIndexes(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` = ?" @@ -538,7 +554,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(","))) + sql += quoter.Join(pkList, ",") sql += " ), " } diff --git a/dialects/oracle.go b/dialects/oracle.go index 4a8162ac..d54ca80c 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -498,6 +498,8 @@ var ( "YEAR": true, "ZONE": true, } + + oracleQuoter = schemas.Quoter{'[', ']', schemas.AlwaysReserve} ) type oracle struct { @@ -505,6 +507,7 @@ type oracle struct { } func (db *oracle) Init(d *core.DB, uri *URI) error { + db.quoter = oracleQuoter return db.Base.Init(d, db, uri) } @@ -549,14 +552,10 @@ func (db *oracle) SupportInsertMany() bool { } func (db *oracle) IsReserved(name string) bool { - _, ok := oracleReservedWords[name] + _, ok := oracleReservedWords[strings.ToUpper(name)] return ok } -func (db *oracle) Quoter() schemas.Quoter { - return schemas.Quoter{"\"", "\""} -} - func (db *oracle) SupportEngine() bool { return false } @@ -601,7 +600,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c if len(pkList) > 0 { sql += "PRIMARY KEY ( " - sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(","))) + sql += quoter.Join(pkList, ",") sql += " ), " } @@ -620,6 +619,23 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c return sql } +func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + var q = oracleQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + var q = oracleQuoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = oracleQuoter + } +} + func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{tableName, idxName} return `SELECT INDEX_NAME FROM USER_INDEXES ` + diff --git a/dialects/postgres.go b/dialects/postgres.go index f92202cd..0049cee6 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -766,6 +766,8 @@ var ( "YES": true, "ZONE": true, } + + postgresQuoter = schemas.Quoter{'"', '"', schemas.AlwaysReserve} ) const postgresPublicSchema = "public" @@ -775,6 +777,7 @@ type postgres struct { } func (db *postgres) Init(d *core.DB, uri *URI) error { + db.quoter = postgresQuoter err := db.Base.Init(d, db, uri) if err != nil { return err @@ -785,6 +788,35 @@ func (db *postgres) Init(d *core.DB, uri *URI) error { return nil } +func (db *postgres) needQuote(name string) bool { + if db.IsReserved(name) { + return true + } + for _, c := range name { + if c >= 'A' && c <= 'Z' { + return true + } + } + return false +} + +func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + var q = postgresQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + var q = postgresQuoter + q.IsReserved = db.needQuote + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = postgresQuoter + } +} + func (db *postgres) DefaultSchema() string { return postgresPublicSchema } @@ -857,14 +889,10 @@ func (db *postgres) SupportInsertMany() bool { } func (db *postgres) IsReserved(name string) bool { - _, ok := postgresReservedWords[name] + _, ok := postgresReservedWords[strings.ToUpper(name)] return ok } -func (db *postgres) Quoter() schemas.Quoter { - return schemas.Quoter{`"`, `"`} -} - func (db *postgres) AutoIncrStr() string { return "" } diff --git a/dialects/quote.go b/dialects/quote.go new file mode 100644 index 00000000..da4e0dd6 --- /dev/null +++ b/dialects/quote.go @@ -0,0 +1,15 @@ +// Copyright 2020 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 + +// QuotePolicy describes quote handle policy +type QuotePolicy int + +// All QuotePolicies +const ( + QuotePolicyAlways QuotePolicy = iota + QuotePolicyNone + QuotePolicyReserved +) diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 39138b13..4af9b27e 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -143,6 +143,8 @@ var ( "WITH": true, "WITHOUT": true, } + + sqlite3Quoter = schemas.Quoter{'`', '`', schemas.AlwaysReserve} ) type sqlite3 struct { @@ -150,9 +152,27 @@ type sqlite3 struct { } func (db *sqlite3) Init(d *core.DB, uri *URI) error { + db.quoter = sqlite3Quoter return db.Base.Init(d, db, uri) } +func (db *sqlite3) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + var q = sqlite3Quoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + var q = sqlite3Quoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = sqlite3Quoter + } +} + func (db *sqlite3) SQLType(c *schemas.Column) string { switch t := c.SQLType.Name; t { case schemas.Bool: @@ -196,14 +216,10 @@ func (db *sqlite3) SupportInsertMany() bool { } func (db *sqlite3) IsReserved(name string) bool { - _, ok := sqlite3ReservedWords[name] + _, ok := sqlite3ReservedWords[strings.ToUpper(name)] return ok } -func (db *sqlite3) Quoter() schemas.Quoter { - return schemas.Quoter{"`", "`"} -} - func (db *sqlite3) AutoIncrStr() string { return "AUTOINCREMENT" } @@ -250,18 +266,24 @@ func (db *sqlite3) ForUpdateSQL(query string) string { } func (db *sqlite3) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { - args := []interface{}{tableName} - query := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" - - rows, err := db.DB().QueryContext(ctx, query, args...) + query := "SELECT * FROM " + tableName + " LIMIT 0" + rows, err := db.DB().QueryContext(ctx, query) if err != nil { return false, err } defer rows.Close() - if rows.Next() { - return true, nil + cols, err := rows.Columns() + if err != nil { + return false, err } + + for _, col := range cols { + if strings.EqualFold(col, colName) { + return true, nil + } + } + return false, nil } diff --git a/engine.go b/engine.go index cc8a74a0..c657cd1f 100644 --- a/engine.go +++ b/engine.go @@ -54,6 +54,10 @@ func (engine *Engine) GetCacher(tableName string) caches.Cacher { return engine.cacherMgr.GetCacher(tableName) } +func (engine *Engine) SetQuotePolicy(quotePolicy dialects.QuotePolicy) { + engine.dialect.SetQuotePolicy(quotePolicy) +} + // BufferSize sets buffer size for iterate func (engine *Engine) BufferSize(size int) *Session { session := engine.NewSession() diff --git a/engine_group.go b/engine_group.go index 8177697e..38b64ca2 100644 --- a/engine_group.go +++ b/engine_group.go @@ -9,6 +9,7 @@ import ( "time" "xorm.io/xorm/caches" + "xorm.io/xorm/dialects" "xorm.io/xorm/log" "xorm.io/xorm/names" ) @@ -180,6 +181,13 @@ func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup { return eg } +func (eg *EngineGroup) SetQuotePolicy(quotePolicy dialects.QuotePolicy) { + eg.Engine.SetQuotePolicy(quotePolicy) + for i := 0; i < len(eg.slaves); i++ { + eg.slaves[i].SetQuotePolicy(quotePolicy) + } +} + // SetTableMapper set the table name mapping rule func (eg *EngineGroup) SetTableMapper(mapper names.Mapper) { eg.Engine.SetTableMapper(mapper) diff --git a/interface.go b/interface.go index 8d2402f0..67b8d4b1 100644 --- a/interface.go +++ b/interface.go @@ -104,6 +104,7 @@ type EngineInterface interface { SetMapper(names.Mapper) SetMaxOpenConns(int) SetMaxIdleConns(int) + SetQuotePolicy(dialects.QuotePolicy) SetSchema(string) SetTableMapper(names.Mapper) SetTZDatabase(tz *time.Location) diff --git a/schemas/quote.go b/schemas/quote.go index 736b774a..10436270 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -8,14 +8,29 @@ import ( "strings" ) -// Quoter represents two quote characters -type Quoter [2]string +// Quoter represents a quoter to the SQL table name and column name +type Quoter struct { + Prefix byte + Suffix byte + IsReserved func(string) bool +} -// CommonQuoter represetns a common quoter -var CommonQuoter = Quoter{"`", "`"} +var ( + // AlwaysFalseReverse always think it's not a reverse word + AlwaysNoReserve = func(string) bool { return false } + + // AlwaysReverse always reverse the word + AlwaysReserve = func(string) bool { return true } + + // CommanQuoteMark represnets the common quote mark + CommanQuoteMark byte = '`' + + // CommonQuoter represetns a common quoter + CommonQuoter = Quoter{CommanQuoteMark, CommanQuoteMark, AlwaysReserve} +) func (q Quoter) IsEmpty() bool { - return q[0] == "" && q[1] == "" + return q.Prefix == 0 && q.Suffix == 0 } func (q Quoter) Quote(s string) string { @@ -24,42 +39,6 @@ func (q Quoter) Quote(s string) string { return buf.String() } -func (q Quoter) Replace(sql string, newQuoter Quoter) string { - if q.IsEmpty() { - return sql - } - - if newQuoter.IsEmpty() { - var buf strings.Builder - for i := 0; i < len(sql); i = i + 1 { - if sql[i] != q[0][0] && sql[i] != q[1][0] { - _ = buf.WriteByte(sql[i]) - } - } - return buf.String() - } - - prefix, suffix := newQuoter[0][0], newQuoter[1][0] - var buf strings.Builder - for i, cnt := 0, 0; i < len(sql); i = i + 1 { - if cnt == 0 && sql[i] == q[0][0] { - _ = buf.WriteByte(prefix) - cnt = 1 - } else if cnt == 1 && sql[i] == q[1][0] { - _ = buf.WriteByte(suffix) - cnt = 0 - } else { - _ = buf.WriteByte(sql[i]) - } - } - return buf.String() -} - -func (q Quoter) ReverseQuote(s string) string { - reverseQuoter := Quoter{q[1], q[0]} - return reverseQuoter.Quote(s) -} - // Trim removes quotes from s func (q Quoter) Trim(s string) string { if len(s) < 2 { @@ -69,10 +48,10 @@ func (q Quoter) Trim(s string) string { var buf strings.Builder for i := 0; i < len(s); i++ { switch { - case i == 0 && s[i:i+1] == q[0]: - case i == len(s)-1 && s[i:i+1] == q[1]: - case s[i:i+1] == q[1] && s[i+1] == '.': - case s[i:i+1] == q[0] && s[i-1] == '.': + case i == 0 && s[i] == q.Prefix: + case i == len(s)-1 && s[i] == q.Suffix: + case s[i] == q.Suffix && s[i+1] == '.': + case s[i] == q.Prefix && s[i-1] == '.': default: buf.WriteByte(s[i]) } @@ -81,31 +60,8 @@ func (q Quoter) Trim(s string) string { } func (q Quoter) Join(a []string, sep string) string { - switch len(a) { - case 0: - return "" - case 1: - return a[0] - } - n := len(sep) * (len(a) - 1) - for i := 0; i < len(a); i++ { - n += len(a[i]) - } - var b strings.Builder - b.Grow(n) - for i, s := range a { - if i > 0 { - b.WriteString(sep) - } - if q[0] != "" && s != "*" { - b.WriteString(q[0]) - } - b.WriteString(strings.TrimSpace(s)) - if q[1] != "" && s != "*" { - b.WriteString(q[1]) - } - } + q.JoinWrite(&b, a, sep) return b.String() } @@ -126,23 +82,113 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error { return err } } - if q[0] != "" && s != "*" && s[0] != '`' { - if _, err := b.WriteString(q[0]); err != nil { - return err - } - } - if _, err := b.WriteString(strings.TrimSpace(s)); err != nil { - return err - } - if q[1] != "" && s != "*" && s[0] != '`' { - if _, err := b.WriteString(q[1]); err != nil { - return err - } + if s != "*" { + q.QuoteTo(b, strings.TrimSpace(s)) } } return nil } +func findWord(v string, start int) int { + for j := start; j < len(v); j++ { + switch v[j] { + case '.', ' ': + return j + } + } + return len(v) +} + +func findStart(value string, start int) int { + if value[start] == '.' { + return start + 1 + } + if value[start] != ' ' { + return start + } + + var k int + for j := start; j < len(value); j++ { + if value[j] != ' ' { + k = j + break + } + } + if k-1 == len(value) { + return len(value) + } + if (value[k] == 'A' || value[k] == 'a') && (value[k+1] == 'S' || value[k+1] == 's') { + k = k + 2 + } + + for j := k; j < len(value); j++ { + if value[j] != ' ' { + return j + } + } + return len(value) +} + +func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error { + var realWord = word + if (word[0] == CommanQuoteMark && word[len(word)-1] == CommanQuoteMark) || + (word[0] == q.Prefix && word[len(word)-1] == q.Suffix) { + realWord = word[1 : len(word)-1] + } + + if q.IsEmpty() { + _, err := buf.WriteString(realWord) + return err + } + + isReserved := q.IsReserved(realWord) + if isReserved { + if err := buf.WriteByte(q.Prefix); err != nil { + return err + } + } + if _, err := buf.WriteString(realWord); err != nil { + return err + } + if isReserved { + return buf.WriteByte(q.Suffix) + } + + return nil +} + +// 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 +func (q Quoter) QuoteTo(buf *strings.Builder, value string) error { + var i int + for i < len(value) { + start := findStart(value, i) + if start > i { + if _, err := buf.WriteString(value[i:start]); err != nil { + return err + } + } + var nextEnd = findWord(value, start) + + if err := q.quoteWordTo(buf, value[start:nextEnd]); err != nil { + return err + } + i = nextEnd + } + return nil +} + +// Strings quotes a slice of string func (q Quoter) Strings(s []string) []string { var res = make([]string, 0, len(s)) for _, a := range s { @@ -150,64 +196,3 @@ func (q Quoter) Strings(s []string) []string { } return res } - -func (q Quoter) QuoteTo(buf *strings.Builder, value string) { - if q.IsEmpty() { - buf.WriteString(value) - return - } - - prefix, suffix := q[0][0], q[1][0] - lastCh := 0 // 0 prefix, 1 char, 2 suffix - i := 0 - for i < len(value) { - // start of a token; might be already quoted - if value[i] == '.' { - _ = buf.WriteByte('.') - lastCh = 1 - i++ - } else if value[i] == prefix || value[i] == '`' { - // Has quotes; skip/normalize `name` to prefix+name+sufix - var ch byte - if value[i] == prefix { - ch = suffix - } else { - ch = '`' - } - _ = buf.WriteByte(prefix) - i++ - lastCh = 0 - for ; i < len(value) && value[i] != ch && value[i] != ' '; i++ { - _ = buf.WriteByte(value[i]) - lastCh = 1 - } - _ = buf.WriteByte(suffix) - lastCh = 2 - i++ - } else if value[i] == ' ' { - if lastCh != 2 { - _ = buf.WriteByte(suffix) - lastCh = 2 - } - - // a AS b or a b - for ; i < len(value); i++ { - if value[i] != ' ' && value[i-1] == ' ' && (len(value) > i+1 && !strings.EqualFold(value[i:i+2], "AS")) { - break - } - - _ = buf.WriteByte(value[i]) - lastCh = 1 - } - } else { - // Requires quotes - _ = buf.WriteByte(prefix) - for ; i < len(value) && value[i] != '.' && value[i] != ' '; i++ { - _ = buf.WriteByte(value[i]) - lastCh = 1 - } - _ = buf.WriteByte(suffix) - lastCh = 2 - } - } -} diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 24739377..c7990f92 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -11,54 +11,125 @@ import ( "github.com/stretchr/testify/assert" ) -func TestQuoteTo(t *testing.T) { - var quoter = Quoter{"[", "]"} +func TestAlwaysQuoteTo(t *testing.T) { + var ( + quoter = Quoter{'[', ']', AlwaysReserve} + kases = []struct { + expected string + value string + }{ + {"[mytable]", "mytable"}, + {"[mytable]", "`mytable`"}, + {"[mytable]", `[mytable]`}, + {`["mytable"]`, `"mytable"`}, + {"[myschema].[mytable]", "myschema.mytable"}, + {"[myschema].[mytable]", "`myschema`.mytable"}, + {"[myschema].[mytable]", "myschema.`mytable`"}, + {"[myschema].[mytable]", "`myschema`.`mytable`"}, + {"[myschema].[mytable]", `[myschema].mytable`}, + {"[myschema].[mytable]", `myschema.[mytable]`}, + {"[myschema].[mytable]", `[myschema].[mytable]`}, + {`["myschema].[mytable"]`, `"myschema.mytable"`}, + {"[message_user] AS [sender]", "`message_user` AS `sender`"}, + {"[myschema].[mytable] AS [table]", "myschema.mytable AS table"}, + } + ) - test := func(t *testing.T, expected string, value string) { - buf := &strings.Builder{} - quoter.QuoteTo(buf, value) - assert.EqualValues(t, expected, buf.String()) + for _, v := range kases { + t.Run(v.value, func(t *testing.T) { + buf := &strings.Builder{} + quoter.QuoteTo(buf, v.value) + assert.EqualValues(t, v.expected, buf.String()) + }) } +} - test(t, "[mytable]", "mytable") - test(t, "[mytable]", "`mytable`") - test(t, "[mytable]", `[mytable]`) +func TestReversedQuoteTo(t *testing.T) { + var ( + quoter = Quoter{'[', ']', func(s string) bool { + if s == "mytable" { + return true + } + return false + }} + kases = []struct { + expected string + value string + }{ + {"[mytable]", "mytable"}, + {"[mytable]", "`mytable`"}, + {"[mytable]", `[mytable]`}, + {`"mytable"`, `"mytable"`}, + {"myschema.[mytable]", "myschema.mytable"}, + {"myschema.[mytable]", "`myschema`.mytable"}, + {"myschema.[mytable]", "myschema.`mytable`"}, + {"myschema.[mytable]", "`myschema`.`mytable`"}, + {"myschema.[mytable]", `[myschema].mytable`}, + {"myschema.[mytable]", `myschema.[mytable]`}, + {"myschema.[mytable]", `[myschema].[mytable]`}, + {`"myschema.mytable"`, `"myschema.mytable"`}, + {"message_user AS sender", "`message_user` AS `sender`"}, + {"myschema.[mytable] AS table", "myschema.mytable AS table"}, + } + ) - test(t, `["mytable"]`, `"mytable"`) + for _, v := range kases { + t.Run(v.value, func(t *testing.T) { + buf := &strings.Builder{} + quoter.QuoteTo(buf, v.value) + assert.EqualValues(t, v.expected, buf.String()) + }) + } +} - test(t, "[myschema].[mytable]", "myschema.mytable") - test(t, "[myschema].[mytable]", "`myschema`.mytable") - test(t, "[myschema].[mytable]", "myschema.`mytable`") - test(t, "[myschema].[mytable]", "`myschema`.`mytable`") - test(t, "[myschema].[mytable]", `[myschema].mytable`) - test(t, "[myschema].[mytable]", `myschema.[mytable]`) - test(t, "[myschema].[mytable]", `[myschema].[mytable]`) +func TestNoQuoteTo(t *testing.T) { + var ( + quoter = Quoter{'[', ']', AlwaysNoReserve} + kases = []struct { + expected string + value string + }{ + {"mytable", "mytable"}, + {"mytable", "`mytable`"}, + {"mytable", `[mytable]`}, + {`"mytable"`, `"mytable"`}, + {"myschema.mytable", "myschema.mytable"}, + {"myschema.mytable", "`myschema`.mytable"}, + {"myschema.mytable", "myschema.`mytable`"}, + {"myschema.mytable", "`myschema`.`mytable`"}, + {"myschema.mytable", `[myschema].mytable`}, + {"myschema.mytable", `myschema.[mytable]`}, + {"myschema.mytable", `[myschema].[mytable]`}, + {`"myschema.mytable"`, `"myschema.mytable"`}, + {"message_user AS sender", "`message_user` AS `sender`"}, + {"myschema.mytable AS table", "myschema.mytable AS table"}, + } + ) - test(t, `["myschema].[mytable"]`, `"myschema.mytable"`) - - test(t, "[message_user] AS [sender]", "`message_user` AS `sender`") - - assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ",")) - - buf := &strings.Builder{} - quoter = Quoter{"", ""} - quoter.QuoteTo(buf, "noquote") - assert.EqualValues(t, "noquote", buf.String()) + for _, v := range kases { + t.Run(v.value, func(t *testing.T) { + buf := &strings.Builder{} + quoter.QuoteTo(buf, v.value) + assert.EqualValues(t, v.expected, buf.String()) + }) + } } func TestJoin(t *testing.T) { cols := []string{"f1", "f2", "f3"} - quoter := Quoter{"[", "]"} + quoter := Quoter{'[', ']', AlwaysReserve} + + assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ",")) assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", ")) - quoter = Quoter{"", ""} + quoter.IsReserved = AlwaysNoReserve assert.EqualValues(t, "f1, f2, f3", quoter.Join(cols, ", ")) } func TestStrings(t *testing.T) { cols := []string{"f1", "f2", "t3.f3"} - quoter := Quoter{"[", "]"} + quoter := Quoter{'[', ']', AlwaysReserve} quotedCols := quoter.Strings(cols) assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols) @@ -72,6 +143,6 @@ func TestTrim(t *testing.T) { for src, dst := range kases { assert.EqualValues(t, src, CommonQuoter.Trim(src)) - assert.EqualValues(t, dst, Quoter{"[", "]"}.Trim(src)) + assert.EqualValues(t, dst, Quoter{'[', ']', AlwaysReserve}.Trim(src)) } } diff --git a/types_null_test.go b/types_null_test.go index 1d5d005e..665849ca 100644 --- a/types_null_test.go +++ b/types_null_test.go @@ -22,7 +22,7 @@ type NullType struct { Age sql.NullInt64 Height sql.NullFloat64 IsMan sql.NullBool `xorm:"null"` - CustomStruct CustomStruct `xorm:"valchar(64) null"` + CustomStruct CustomStruct `xorm:"varchar(64) null"` } type CustomStruct struct { @@ -58,14 +58,12 @@ func (m CustomStruct) Value() (driver.Value, error) { func TestCreateNullStructTable(t *testing.T) { assert.NoError(t, prepareEngine()) - err := testEngine.CreateTables(new(NullType)) assert.NoError(t, err) } func TestDropNullStructTable(t *testing.T) { assert.NoError(t, prepareEngine()) - err := testEngine.DropTables(new(NullType)) assert.NoError(t, err) } @@ -78,7 +76,7 @@ func TestNullStructInsert(t *testing.T) { item := new(NullType) _, err := testEngine.Insert(item) assert.NoError(t, err) - assert.EqualValues(t, item.Id, 1) + assert.EqualValues(t, 1, item.Id) } if true { @@ -90,12 +88,11 @@ func TestNullStructInsert(t *testing.T) { } _, err := testEngine.Insert(&item) assert.NoError(t, err) - assert.EqualValues(t, item.Id, 2) + assert.EqualValues(t, 2, item.Id) } if true { items := []NullType{} - for i := 0; i < 5; i++ { item := NullType{ Name: sql.NullString{String: "haolei_" + fmt.Sprint(i+1), Valid: true}, @@ -152,7 +149,7 @@ func TestNullStructUpdate(t *testing.T) { affected, err := testEngine.ID(2).Cols("age", "height", "is_man").Update(item) assert.NoError(t, err) - assert.EqualValues(t, affected, 1) + assert.EqualValues(t, 1, affected) } if true { // 测试In update @@ -160,7 +157,7 @@ func TestNullStructUpdate(t *testing.T) { 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) - assert.EqualValues(t, affected, 2) + assert.EqualValues(t, 2, affected) } if true { // 测试where @@ -183,9 +180,7 @@ func TestNullStructUpdate(t *testing.T) { _, err := testEngine.AllCols().ID(6).Update(item) assert.NoError(t, err) - fmt.Println(item) } - } func TestNullStructFind(t *testing.T) { @@ -274,9 +269,8 @@ func TestNullStructCount(t *testing.T) { if true { item := new(NullType) - total, err := testEngine.Where("age IS NOT NULL").Count(item) + _, err := testEngine.Where("age IS NOT NULL").Count(item) assert.NoError(t, err) - fmt.Println(total) } } @@ -292,7 +286,6 @@ func TestNullStructRows(t *testing.T) { for rows.Next() { err = rows.Scan(item) assert.NoError(t, err) - fmt.Println(item) } } diff --git a/xorm_test.go b/xorm_test.go index c1f38757..c9a1a74b 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -18,6 +18,7 @@ import ( _ "github.com/mattn/go-sqlite3" _ "github.com/ziutek/mymysql/godrv" "xorm.io/xorm/caches" + "xorm.io/xorm/dialects" "xorm.io/xorm/log" "xorm.io/xorm/names" "xorm.io/xorm/schemas" @@ -38,6 +39,7 @@ var ( schema = flag.String("schema", "", "specify the schema") ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb") ingoreUpdateLimit = flag.Bool("ignore_update_limit", false, "ignore update limit if implementation difference, only for cockroach") + quotePolicyStr = flag.String("quote", "always", "quote could be always, none, reversed") tableMapper names.Mapper colMapper names.Mapper ) @@ -131,6 +133,14 @@ func createEngine(dbType, connStr string) error { testEngine.SetMapper(names.LintGonicMapper) } } + + if *quotePolicyStr == "none" { + testEngine.SetQuotePolicy(dialects.QuotePolicyNone) + } else if *quotePolicyStr == "reserved" { + testEngine.SetQuotePolicy(dialects.QuotePolicyReserved) + } else { + testEngine.SetQuotePolicy(dialects.QuotePolicyAlways) + } } tableMapper = testEngine.GetTableMapper() From fa219bb83641ed2c5276f07e2dc69401a3812b7a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 6 Mar 2020 08:55:12 +0000 Subject: [PATCH 053/112] Fix map with cols (#1575) Fix cache bug Fix map with cols Reviewed-on: https://gitea.com/xorm/xorm/pulls/1575 --- internal/statements/column_map.go | 10 ++++++++- internal/statements/statement.go | 2 +- session_find.go | 13 ++++++++--- session_find_test.go | 37 ++++++++++++++++++++----------- session_get.go | 2 +- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/internal/statements/column_map.go b/internal/statements/column_map.go index 8440f821..bb764b4e 100644 --- a/internal/statements/column_map.go +++ b/internal/statements/column_map.go @@ -30,7 +30,15 @@ func (m columnMap) Contain(colName string) bool { return false } -func (m *columnMap) add(colName string) bool { +func (m columnMap) Len() int { + return len(m) +} + +func (m columnMap) IsEmpty() bool { + return len(m) == 0 +} + +func (m *columnMap) Add(colName string) bool { if m.Contain(colName) { return false } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 90771d4b..a2a356ff 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -389,7 +389,7 @@ func col2NewCols(columns ...string) []string { func (statement *Statement) Cols(columns ...string) *Statement { cols := col2NewCols(columns...) for _, nc := range cols { - statement.ColumnMap.add(nc) + statement.ColumnMap.Add(nc) } return statement } diff --git a/session_find.go b/session_find.go index 9551b767..72882a28 100644 --- a/session_find.go +++ b/session_find.go @@ -65,13 +65,14 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error { defer session.resetStatement() - if session.statement.LastError != nil { return session.statement.LastError } sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) - if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map { + var isSlice = sliceValue.Kind() == reflect.Slice + var isMap = sliceValue.Kind() == reflect.Map + if !isSlice && !isMap { return errors.New("needs a pointer to a slice or a map") } @@ -127,12 +128,18 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } } + if isMap && !session.statement.ColumnMap.IsEmpty() { + for _, k := range session.statement.RefTable.PrimaryKeys { + session.statement.ColumnMap.Add(k) + } + } + sqlStr, args, err := session.statement.GenFindSQL(autoCond) if err != nil { return err } - if session.canCache() { + if session.statement.ColumnMap.IsEmpty() && session.canCache() { if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && !session.statement.IsDistinct && !session.statement.GetUnscoped() { diff --git a/session_find_test.go b/session_find_test.go index 2d15eed8..28ba1d60 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -5,13 +5,13 @@ package xorm import ( - "fmt" "testing" "time" - "github.com/stretchr/testify/assert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" + + "github.com/stretchr/testify/assert" ) func TestJoinLimit(t *testing.T) { @@ -79,11 +79,9 @@ func TestWhere(t *testing.T) { users := make([]Userinfo, 0) err := testEngine.Where("id > ?", 2).Find(&users) assert.NoError(t, err) - fmt.Println(users) err = testEngine.Where("id > ?", 2).And("id < ?", 10).Find(&users) assert.NoError(t, err) - fmt.Println(users) } func TestFind(t *testing.T) { @@ -94,9 +92,6 @@ func TestFind(t *testing.T) { err := testEngine.Find(&users) assert.NoError(t, err) - for _, user := range users { - fmt.Println(user) - } users2 := make([]Userinfo, 0) var tbName = testEngine.Quote(testEngine.TableName(new(Userinfo), true)) @@ -112,10 +107,6 @@ func TestFind2(t *testing.T) { err := testEngine.Find(&users) assert.NoError(t, err) - - for _, user := range users { - fmt.Println(user) - } } type Team struct { @@ -191,9 +182,29 @@ func TestFindMap(t *testing.T) { assert.NoError(t, prepareEngine()) assertSync(t, new(Userinfo)) - users := make(map[int64]Userinfo) - err := testEngine.Find(&users) + cnt, err := testEngine.Insert(&Userinfo{ + Username: "lunny", + Departname: "depart1", + IsMan: true, + }) assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + users := make(map[int64]Userinfo) + err = testEngine.Find(&users) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(users)) + assert.EqualValues(t, "lunny", users[1].Username) + assert.EqualValues(t, "depart1", users[1].Departname) + assert.True(t, users[1].IsMan) + + users = make(map[int64]Userinfo) + err = testEngine.Cols("username, departname").Find(&users) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(users)) + assert.EqualValues(t, "lunny", users[1].Username) + assert.EqualValues(t, "depart1", users[1].Departname) + assert.False(t, users[1].IsMan) } func TestFindMap2(t *testing.T) { diff --git a/session_get.go b/session_get.go index c468b440..76918194 100644 --- a/session_get.go +++ b/session_get.go @@ -65,7 +65,7 @@ func (session *Session) get(bean interface{}) (bool, error) { table := session.statement.RefTable - if session.canCache() && beanValue.Elem().Kind() == reflect.Struct { + if session.statement.ColumnMap.IsEmpty() && session.canCache() && beanValue.Elem().Kind() == reflect.Struct { if cacher := session.engine.GetCacher(session.statement.TableName()); cacher != nil && !session.statement.GetUnscoped() { has, err := session.cacheGet(bean, sqlStr, args...) From 0f166d82da7f0410571cdb96106ad28a4022863b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 6 Mar 2020 12:08:32 +0000 Subject: [PATCH 054/112] Fix rows bug (#1576) Fix rows bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1576 --- rows.go | 38 +++++++++++++++++++++++++++++++++++++- rows_test.go | 16 +++++++++++++++- session_raw.go | 3 +++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/rows.go b/rows.go index e14c9894..aa5e66e3 100644 --- a/rows.go +++ b/rows.go @@ -6,9 +6,11 @@ package xorm import ( "database/sql" + "errors" "fmt" "reflect" + "xorm.io/builder" "xorm.io/xorm/core" "xorm.io/xorm/internal/utils" ) @@ -30,6 +32,13 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { var args []interface{} var err error + beanValue := reflect.ValueOf(bean) + if beanValue.Kind() != reflect.Ptr { + return nil, errors.New("needs a pointer to a value") + } else if beanValue.Elem().Kind() == reflect.Ptr { + return nil, errors.New("a pointer to a pointer is not allowed") + } + if err = rows.session.statement.SetRefBean(bean); err != nil { return nil, err } @@ -39,7 +48,34 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { } if rows.session.statement.RawSQL == "" { - sqlStr, args, err = rows.session.statement.GenGetSQL(bean) + var autoCond builder.Cond + var addedTableName = (len(session.statement.JoinStr) > 0) + var table = rows.session.statement.RefTable + + if !session.statement.NoAutoCondition { + var err error + autoCond, err = session.statement.BuildConds(table, bean, true, true, false, true, addedTableName) + if err != nil { + return nil, err + } + } else { + // !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) + } + } + + sqlStr, args, err = rows.session.statement.GenFindSQL(autoCond) if err != nil { return nil, err } diff --git a/rows_test.go b/rows_test.go index af333861..439d3793 100644 --- a/rows_test.go +++ b/rows_test.go @@ -104,7 +104,6 @@ func TestRowsMyTableName(t *testing.T) { rows, err := testEngine.Table(tableName).Rows(new(UserRowsMyTable)) assert.NoError(t, err) - defer rows.Close() cnt = 0 user := new(UserRowsMyTable) @@ -114,6 +113,21 @@ func TestRowsMyTableName(t *testing.T) { cnt++ } assert.EqualValues(t, 1, cnt) + + rows.Close() + + rows, err = testEngine.Table(tableName).Rows(&UserRowsMyTable{ + Id: 2, + }) + assert.NoError(t, err) + cnt = 0 + user = new(UserRowsMyTable) + for rows.Next() { + err = rows.Scan(user) + assert.NoError(t, err) + cnt++ + } + assert.EqualValues(t, 0, cnt) } type UserRowsSpecTable struct { diff --git a/session_raw.go b/session_raw.go index 02dcbf56..0cea60b7 100644 --- a/session_raw.go +++ b/session_raw.go @@ -23,6 +23,9 @@ func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Rows, error) { defer session.resetStatement() + if session.statement.LastError != nil { + return nil, session.statement.LastError + } session.queryPreprocess(&sqlStr, args...) From 7f22948be93e8fb6428b0cefeb30677ce083905b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 7 Mar 2020 08:51:30 +0000 Subject: [PATCH 055/112] Improve dialect interface (#1578) Improve dialect interface Reviewed-on: https://gitea.com/xorm/xorm/pulls/1578 --- dialects/dialect.go | 101 ++++---------------------- dialects/mssql.go | 20 +---- dialects/mysql.go | 19 +---- dialects/oracle.go | 25 +------ dialects/postgres.go | 42 +++++++++-- dialects/sqlite3.go | 50 ++++++++++--- dialects/time.go | 2 +- engine.go | 8 +- internal/statements/cache.go | 6 +- internal/statements/query.go | 18 ++--- internal/statements/statement.go | 26 ++----- internal/statements/statement_args.go | 4 +- session_cols_test.go | 2 +- session_convert.go | 6 +- session_delete.go | 4 +- session_delete_test.go | 4 +- session_get_test.go | 6 +- session_insert.go | 13 ++-- session_query_test.go | 8 +- session_schema.go | 8 +- session_update.go | 12 +-- tags_test.go | 14 ++-- types_test.go | 4 +- 23 files changed, 162 insertions(+), 240 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index d89f1ebe..b074d485 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -34,7 +34,6 @@ type Dialect interface { Init(*core.DB, *URI) error URI() *URI DB() *core.DB - DBType() schemas.DBType SQLType(*schemas.Column) string FormatBytes(b []byte) string DefaultSchema() string @@ -44,33 +43,26 @@ type Dialect interface { SetQuotePolicy(quotePolicy QuotePolicy) AutoIncrStr() string - SupportInsertMany() bool - SupportEngine() bool - SupportCharset() bool SupportDropIfExists() bool - IndexOnTable() bool - ShowCreateNull() bool + GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) IndexCheckSQL(tableName, idxName string) (string, []interface{}) - TableCheckSQL(tableName string) (string, []interface{}) - - IsColumnExist(ctx context.Context, tableName string, colName string) (bool, error) - - CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string - DropTableSQL(tableName string) string CreateIndexSQL(tableName string, index *schemas.Index) string DropIndexSQL(tableName string, index *schemas.Index) string + GetTables(ctx context.Context) ([]*schemas.Table, error) + TableCheckSQL(tableName string) (string, []interface{}) + CreateTableSQL(table *schemas.Table, tableName string) string + DropTableSQL(tableName string) string + + GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) + IsColumnExist(ctx context.Context, tableName string, colName string) (bool, error) AddColumnSQL(tableName string, col *schemas.Column) string ModifyColumnSQL(tableName string, col *schemas.Column) string ForUpdateSQL(query string) string - GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) - GetTables(ctx context.Context) ([]*schemas.Table, error) - GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) - Filters() []Filter SetParams(params map[string]string) } @@ -125,12 +117,10 @@ func (b *Base) String(col *schemas.Column) string { sql += "DEFAULT " + col.Default + " " } - if b.dialect.ShowCreateNull() { - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " } return sql @@ -146,12 +136,10 @@ func (b *Base) StringNoPk(col *schemas.Column) string { sql += "DEFAULT " + col.Default + " " } - if b.dialect.ShowCreateNull() { - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " } return sql @@ -161,10 +149,6 @@ func (b *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } -func (b *Base) ShowCreateNull() bool { - return true -} - func (db *Base) SupportDropIfExists() bool { return true } @@ -234,59 +218,6 @@ func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, db.StringNoPk(col)) } -func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { - var sql string - sql = "CREATE TABLE IF NOT EXISTS " - if tableName == "" { - tableName = table.Name - } - - quoter := b.dialect.Quoter() - sql += quoter.Quote(tableName) - sql += " (" - - if len(table.ColumnsSeq()) > 0 { - pkList := table.PrimaryKeys - - for _, colName := range table.ColumnsSeq() { - col := table.GetColumn(colName) - if col.IsPrimaryKey && len(pkList) == 1 { - sql += b.String(col) - } else { - sql += b.StringNoPk(col) - } - sql = strings.TrimSpace(sql) - if b.DBType() == schemas.MYSQL && len(col.Comment) > 0 { - sql += " COMMENT '" + col.Comment + "'" - } - sql += ", " - } - - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += quoter.Join(pkList, ",") - sql += " ), " - } - - sql = sql[:len(sql)-2] - } - sql += ")" - - if b.dialect.SupportEngine() && storeEngine != "" { - sql += " ENGINE=" + storeEngine - } - if b.dialect.SupportCharset() { - if len(charset) == 0 { - charset = b.dialect.URI().Charset - } - if len(charset) > 0 { - sql += " DEFAULT CHARSET " + charset - } - } - - return sql -} - func (b *Base) ForUpdateSQL(query string) string { return query + " FOR UPDATE" } diff --git a/dialects/mssql.go b/dialects/mssql.go index a2cbb361..06ab0b78 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -307,10 +307,6 @@ func (db *mssql) SetQuotePolicy(quotePolicy QuotePolicy) { } } -func (db *mssql) SupportEngine() bool { - return false -} - func (db *mssql) AutoIncrStr() string { return "IDENTITY" } @@ -321,26 +317,12 @@ func (db *mssql) DropTableSQL(tableName string) string { "DROP TABLE \"%s\"", tableName, tableName) } -func (db *mssql) SupportCharset() bool { - return false -} - -func (db *mssql) IndexOnTable() bool { - return true -} - func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" return sql, args } -/*func (db *mssql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{tableName, colName} - sql := `SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "COLUMN_NAME" = ?` - return sql, args -}*/ - func (db *mssql) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { query := `SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "COLUMN_NAME" = ?` @@ -509,7 +491,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? return indexes, nil } -func (db *mssql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { +func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) string { var sql string if tableName == "" { tableName = table.Name diff --git a/dialects/mysql.go b/dialects/mysql.go index 5f36ed31..364f22b6 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -279,22 +279,10 @@ func (db *mysql) IsReserved(name string) bool { return ok } -func (db *mysql) SupportEngine() bool { - return true -} - func (db *mysql) AutoIncrStr() string { return "AUTO_INCREMENT" } -func (db *mysql) SupportCharset() bool { - return true -} - -func (db *mysql) IndexOnTable() bool { - return true -} - func (db *mysql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{db.uri.DBName, tableName, idxName} sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" @@ -524,7 +512,7 @@ func (db *mysql) GetIndexes(ctx context.Context, tableName string) (map[string]* return indexes, nil } -func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { +func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) string { var sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { tableName = table.Name @@ -562,10 +550,11 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch } sql += ")" - if storeEngine != "" { - sql += " ENGINE=" + storeEngine + if table.StoreEngine != "" { + sql += " ENGINE=" + table.StoreEngine } + var charset = table.Charset if len(charset) == 0 { charset = db.URI().Charset } diff --git a/dialects/oracle.go b/dialects/oracle.go index d54ca80c..e0d83115 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -556,27 +556,15 @@ func (db *oracle) IsReserved(name string) bool { return ok } -func (db *oracle) SupportEngine() bool { - return false -} - -func (db *oracle) SupportCharset() bool { - return false -} - func (db *oracle) SupportDropIfExists() bool { return false } -func (db *oracle) IndexOnTable() bool { - return false -} - func (db *oracle) DropTableSQL(tableName string) string { return fmt.Sprintf("DROP TABLE `%s`", tableName) } -func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { +func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) string { var sql = "CREATE TABLE " if tableName == "" { tableName = table.Name @@ -605,17 +593,6 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c } sql = sql[:len(sql)-2] + ")" - if db.SupportEngine() && storeEngine != "" { - sql += " ENGINE=" + storeEngine - } - if db.SupportCharset() { - if len(charset) == 0 { - charset = db.URI().Charset - } - if len(charset) > 0 { - sql += " DEFAULT CHARSET " + charset - } - } return sql } diff --git a/dialects/postgres.go b/dialects/postgres.go index 0049cee6..31cd49b6 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -897,16 +897,42 @@ func (db *postgres) AutoIncrStr() string { return "" } -func (db *postgres) SupportEngine() bool { - return false -} +func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) string { + var sql string + sql = "CREATE TABLE IF NOT EXISTS " + if tableName == "" { + tableName = table.Name + } -func (db *postgres) SupportCharset() bool { - return false -} + quoter := db.Quoter() + sql += quoter.Quote(tableName) + sql += " (" -func (db *postgres) IndexOnTable() bool { - return false + if len(table.ColumnsSeq()) > 0 { + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += db.String(col) + } else { + sql += db.StringNoPk(col) + } + sql = strings.TrimSpace(sql) + sql += ", " + } + + if len(pkList) > 1 { + sql += "PRIMARY KEY ( " + sql += quoter.Join(pkList, ",") + sql += " ), " + } + + sql = sql[:len(sql)-2] + } + sql += ")" + + return sql } func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 4af9b27e..212c5a8e 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -224,18 +224,6 @@ func (db *sqlite3) AutoIncrStr() string { return "AUTOINCREMENT" } -func (db *sqlite3) SupportEngine() bool { - return false -} - -func (db *sqlite3) SupportCharset() bool { - return false -} - -func (db *sqlite3) IndexOnTable() bool { - return false -} - func (db *sqlite3) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args @@ -261,6 +249,44 @@ 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 { + 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) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += db.String(col) + } else { + sql += db.StringNoPk(col) + } + sql = strings.TrimSpace(sql) + sql += ", " + } + + if len(pkList) > 1 { + sql += "PRIMARY KEY ( " + sql += quoter.Join(pkList, ",") + sql += " ), " + } + + sql = sql[:len(sql)-2] + } + sql += ")" + + return sql +} + func (db *sqlite3) ForUpdateSQL(query string) string { return query } diff --git a/dialects/time.go b/dialects/time.go index 022dc960..b0394745 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -21,7 +21,7 @@ func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{} case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. v = t.Format("2006-01-02 15:04:05") case schemas.TimeStampz: - if dialect.DBType() == schemas.MSSQL { + if dialect.URI().DBType == schemas.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") } else { v = t.Format(time.RFC3339Nano) diff --git a/engine.go b/engine.go index c657cd1f..c330e9f5 100644 --- a/engine.go +++ b/engine.go @@ -365,7 +365,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch var distDBName string if len(tp) == 0 { dialect = engine.dialect - distDBName = string(engine.dialect.DBType()) + distDBName = string(engine.dialect.URI().DBType) } else { dialect = dialects.QueryDialect(tp[0]) if dialect == nil { @@ -376,7 +376,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } _, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm v%s %s, from %s to %s*/\n\n", - Version, time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.DBType(), strings.ToUpper(distDBName))) + Version, time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.URI().DBType, strings.ToUpper(distDBName))) if err != nil { return err } @@ -388,7 +388,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } - _, err = io.WriteString(w, dialect.CreateTableSQL(table, "", table.StoreEngine, "")+";\n") + _, err = io.WriteString(w, dialect.CreateTableSQL(table, "")+";\n") if err != nil { return err } @@ -486,7 +486,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } // FIXME: Hack for postgres - if dialect.DBType() == schemas.POSTGRES && table.AutoIncrColumn() != nil { + if dialect.URI().DBType == schemas.POSTGRES && table.AutoIncrColumn() != nil { _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quoter().Quote(table.Name)+"), 1), false);\n") if err != nil { return err diff --git a/internal/statements/cache.go b/internal/statements/cache.go index d7f72318..cb33df08 100644 --- a/internal/statements/cache.go +++ b/internal/statements/cache.go @@ -27,7 +27,7 @@ func (statement *Statement) ConvertIDSQL(sqlStr string) string { var top string pLimitN := statement.LimitN - if pLimitN != nil && statement.dialect.DBType() == schemas.MSSQL { + if pLimitN != nil && statement.dialect.URI().DBType == schemas.MSSQL { top = fmt.Sprintf("TOP %d ", *pLimitN) } @@ -56,9 +56,9 @@ func (statement *Statement) ConvertUpdateSQL(sqlStr string) (string, string) { // TODO: for postgres only, if any other database? var paraStr string - if statement.dialect.DBType() == schemas.POSTGRES { + if statement.dialect.URI().DBType == schemas.POSTGRES { paraStr = "$" - } else if statement.dialect.DBType() == schemas.MSSQL { + } else if statement.dialect.URI().DBType == schemas.MSSQL { paraStr = ":" } diff --git a/internal/statements/query.go b/internal/statements/query.go index a058f752..8f7aeebb 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -201,14 +201,14 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB whereStr = " WHERE " + condSQL } - if dialect.DBType() == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { + if dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { fromStr += statement.TableName() } else { fromStr += quote(statement.TableName()) } if statement.TableAlias != "" { - if dialect.DBType() == schemas.ORACLE { + if dialect.URI().DBType == schemas.ORACLE { fromStr += " " + quote(statement.TableAlias) } else { fromStr += " AS " + quote(statement.TableAlias) @@ -219,7 +219,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB } pLimitN := statement.LimitN - if dialect.DBType() == schemas.MSSQL { + if dialect.URI().DBType == schemas.MSSQL { if pLimitN != nil { LimitNValue := *pLimitN top = fmt.Sprintf("TOP %d ", LimitNValue) @@ -281,7 +281,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) } if needLimit { - if dialect.DBType() != schemas.MSSQL && dialect.DBType() != schemas.ORACLE { + 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) @@ -291,7 +291,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB } else if pLimitN != nil { fmt.Fprint(&buf, " LIMIT ", *pLimitN) } - } else if dialect.DBType() == schemas.ORACLE { + } else if dialect.URI().DBType == schemas.ORACLE { if statement.Start != 0 || pLimitN != nil { oldString := buf.String() buf.Reset() @@ -337,18 +337,18 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac return "", nil, err } - if statement.dialect.DBType() == schemas.MSSQL { + 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.DBType() == schemas.ORACLE { + } 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) } args = condArgs } else { - if statement.dialect.DBType() == schemas.MSSQL { + if statement.dialect.URI().DBType == schemas.MSSQL { sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) - } else if statement.dialect.DBType() == schemas.ORACLE { + } 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) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index a2a356ff..d6dd58b1 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -641,8 +641,9 @@ func (statement *Statement) genColumnStr() string { } func (statement *Statement) GenCreateTableSQL() string { - return statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName(), - statement.StoreEngine, statement.Charset) + statement.RefTable.StoreEngine = statement.StoreEngine + statement.RefTable.Charset = statement.Charset + return statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName()) } func (statement *Statement) GenIndexSQL() []string { @@ -680,20 +681,8 @@ func (statement *Statement) GenDelIndexSQL() []string { if idx > -1 { tbName = tbName[idx+1:] } - idxPrefixName := strings.Replace(tbName, `"`, "", -1) - idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1) - for idxName, index := range statement.RefTable.Indexes { - var rIdxName string - if index.Type == schemas.UniqueType { - rIdxName = uniqueName(idxPrefixName, idxName) - } else if index.Type == schemas.IndexType { - rIdxName = utils.IndexName(idxPrefixName, idxName) - } - sql := fmt.Sprintf("DROP INDEX %v", statement.quote(dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), rIdxName, true))) - if statement.dialect.IndexOnTable() { - sql += fmt.Sprintf(" ON %v", statement.quote(tbName)) - } - sqls = append(sqls, sql) + for _, index := range statement.RefTable.Indexes { + sqls = append(sqls, statement.dialect.DropIndexSQL(tbName, index)) } return sqls } @@ -714,7 +703,8 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, continue } - if statement.dialect.DBType() == schemas.MSSQL && (col.SQLType.Name == schemas.Text || col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { + if statement.dialect.URI().DBType == schemas.MSSQL && (col.SQLType.Name == schemas.Text || + col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { continue } if col.SQLType.IsJson() { @@ -1002,7 +992,7 @@ func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { cond = builder.Eq{colName: 0} } else { // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. - if statement.dialect.DBType() != schemas.MSSQL { + if statement.dialect.URI().DBType != schemas.MSSQL { cond = builder.Eq{colName: utils.ZeroTime1} } } diff --git a/internal/statements/statement_args.go b/internal/statements/statement_args.go index 8eee246e..7d1ef9eb 100644 --- a/internal/statements/statement_args.go +++ b/internal/statements/statement_args.go @@ -80,7 +80,7 @@ const insertSelectPlaceHolder = true func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case bool: - if statement.dialect.DBType() == schemas.MSSQL { + if statement.dialect.URI().DBType == schemas.MSSQL { if argv { if _, err := w.WriteString("1"); err != nil { return err @@ -119,7 +119,7 @@ func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) er w.Append(arg) } else { var convertFunc = convertStringSingleQuote - if statement.dialect.DBType() == schemas.MYSQL { + if statement.dialect.URI().DBType == schemas.MYSQL { convertFunc = convertString } if _, err := w.WriteString(convertArg(arg, convertFunc)); err != nil { diff --git a/session_cols_test.go b/session_cols_test.go index 58b4e841..2847cf35 100644 --- a/session_cols_test.go +++ b/session_cols_test.go @@ -45,7 +45,7 @@ func TestSetExpr(t *testing.T) { assert.EqualValues(t, 1, cnt) var not = "NOT" - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { not = "~" } cnt, err = testEngine.SetExpr("show", not+" `show`").ID(1).Update(new(UserExpr)) diff --git a/session_convert.go b/session_convert.go index 0776bc45..28866d4d 100644 --- a/session_convert.go +++ b/session_convert.go @@ -65,7 +65,7 @@ func (session *Session) str2Time(col *schemas.Column, data string) (outTime time } sdata = strings.TrimSpace(sdata) - if session.engine.dialect.DBType() == schemas.MYSQL && len(sdata) > 8 { + if session.engine.dialect.URI().DBType == schemas.MYSQL && len(sdata) > 8 { sdata = sdata[len(sdata)-8:] } @@ -159,7 +159,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val var err error // for mysql, when use bit, it returned \x01 if col.SQLType.Name == schemas.Bit && - session.engine.dialect.DBType() == schemas.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API + 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 { @@ -399,7 +399,7 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val var err error // for mysql, when use bit, it returned \x01 if col.SQLType.Name == schemas.Bit && - session.engine.dialect.DBType() == schemas.MYSQL { + session.engine.dialect.URI().DBType == schemas.MYSQL { if len(data) == 1 { x = int32(data[0]) } else { diff --git a/session_delete.go b/session_delete.go index eb5e2aea..ff28867a 100644 --- a/session_delete.go +++ b/session_delete.go @@ -135,7 +135,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { } if len(orderSQL) > 0 { - switch session.engine.dialect.DBType() { + 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 { @@ -176,7 +176,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { condSQL) if len(orderSQL) > 0 { - switch session.engine.dialect.DBType() { + 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 { diff --git a/session_delete_test.go b/session_delete_test.go index d7fb3110..6fba860b 100644 --- a/session_delete_test.go +++ b/session_delete_test.go @@ -28,7 +28,7 @@ func TestDelete(t *testing.T) { defer session.Close() var err error - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT userinfo_delete ON") @@ -40,7 +40,7 @@ func TestDelete(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/session_get_test.go b/session_get_test.go index 7e10bf54..b83a118b 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -154,7 +154,7 @@ func TestGetVar(t *testing.T) { assert.Equal(t, "1.5", fmt.Sprintf("%.1f", money)) var money2 float64 - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { has, err = testEngine.SQL("SELECT TOP 1 money FROM " + testEngine.TableName("get_var", true)).Get(&money2) } else { has, err = testEngine.SQL("SELECT money FROM " + testEngine.TableName("get_var", true) + " LIMIT 1").Get(&money2) @@ -234,7 +234,7 @@ func TestGetStruct(t *testing.T) { defer session.Close() var err error - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT userinfo_get ON") @@ -243,7 +243,7 @@ func TestGetStruct(t *testing.T) { cnt, err := session.Insert(&UserinfoGet{Uid: 2}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/session_insert.go b/session_insert.go index 4662e25a..e5368571 100644 --- a/session_insert.go +++ b/session_insert.go @@ -254,7 +254,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error quoter := session.engine.dialect.Quoter() var sql string colStr := quoter.Join(colNames, ",") - if session.engine.dialect.DBType() == schemas.ORACLE { + if session.engine.dialect.URI().DBType == schemas.ORACLE { temp := fmt.Sprintf(") INTO %s (%v) VALUES (", quoter.Quote(tableName), colStr) @@ -361,7 +361,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { var tableName = session.statement.TableName() var output string - if session.engine.dialect.DBType() == schemas.MSSQL && len(table.AutoIncrement) > 0 { + if session.engine.dialect.URI().DBType == schemas.MSSQL && len(table.AutoIncrement) > 0 { output = fmt.Sprintf(" OUTPUT Inserted.%s", table.AutoIncrement) } @@ -371,7 +371,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } if len(colPlaces) <= 0 { - if session.engine.dialect.DBType() == schemas.MYSQL { + if session.engine.dialect.URI().DBType == schemas.MYSQL { if _, err := buf.WriteString(" VALUES ()"); err != nil { return 0, err } @@ -433,7 +433,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } } - if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == schemas.POSTGRES { + if len(table.AutoIncrement) > 0 && session.engine.dialect.URI().DBType == schemas.POSTGRES { if _, err := buf.WriteString(" RETURNING " + session.engine.Quote(table.AutoIncrement)); err != nil { return 0, err } @@ -472,7 +472,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { // for postgres, many of them didn't implement lastInsertId, so we should // implemented it ourself. - if session.engine.dialect.DBType() == schemas.ORACLE && len(table.AutoIncrement) > 0 { + if session.engine.dialect.URI().DBType == schemas.ORACLE && len(table.AutoIncrement) > 0 { res, err := session.queryBytes("select seq_atable.currval from dual", args...) if err != nil { return 0, err @@ -513,7 +513,8 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue.Set(int64ToIntValue(id, aiValue.Type())) return 1, nil - } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.DBType() == schemas.POSTGRES || session.engine.dialect.DBType() == schemas.MSSQL) { + } 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 { diff --git a/session_query_test.go b/session_query_test.go index e4635d64..bed62be0 100644 --- a/session_query_test.go +++ b/session_query_test.go @@ -207,7 +207,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) - if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.POSTGRES || testEngine.Dialect().URI().DBType == schemas.MSSQL { assert.EqualValues(t, "false", records[0]["msg"]) } else { assert.EqualValues(t, "0", records[0]["msg"]) @@ -217,7 +217,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) - if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.POSTGRES || testEngine.Dialect().URI().DBType == schemas.MSSQL { assert.EqualValues(t, "false", records[0]["msg"]) } else { assert.EqualValues(t, "0", records[0]["msg"]) @@ -244,7 +244,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) - if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.POSTGRES || testEngine.Dialect().URI().DBType == schemas.MSSQL { assert.EqualValues(t, "false", records[0][1]) } else { assert.EqualValues(t, "0", records[0][1]) @@ -254,7 +254,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) - if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.POSTGRES || testEngine.Dialect().URI().DBType == schemas.MSSQL { assert.EqualValues(t, "false", records[0][1]) } else { assert.EqualValues(t, "0", records[0][1]) diff --git a/session_schema.go b/session_schema.go index 3617a6b8..6d363521 100644 --- a/session_schema.go +++ b/session_schema.go @@ -313,8 +313,8 @@ func (session *Session) Sync2(beans ...interface{}) error { if expectedType == schemas.Text && strings.HasPrefix(curType, schemas.Varchar) { // currently only support mysql & postgres - if engine.dialect.DBType() == schemas.MYSQL || - engine.dialect.DBType() == schemas.POSTGRES { + if engine.dialect.URI().DBType == schemas.MYSQL || + engine.dialect.URI().DBType == schemas.POSTGRES { engine.logger.Infof("Table %s column %s change type from %s to %s\n", tbNameWithSchema, col.Name, curType, expectedType) _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) @@ -323,7 +323,7 @@ func (session *Session) Sync2(beans ...interface{}) error { tbNameWithSchema, col.Name, curType, expectedType) } } else if strings.HasPrefix(curType, schemas.Varchar) && strings.HasPrefix(expectedType, schemas.Varchar) { - if engine.dialect.DBType() == schemas.MYSQL { + if engine.dialect.URI().DBType == schemas.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", tbNameWithSchema, col.Name, oriCol.Length, col.Length) @@ -337,7 +337,7 @@ func (session *Session) Sync2(beans ...interface{}) error { } } } else if expectedType == schemas.Varchar { - if engine.dialect.DBType() == schemas.MYSQL { + if engine.dialect.URI().DBType == schemas.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", tbNameWithSchema, col.Name, oriCol.Length, col.Length) diff --git a/session_update.go b/session_update.go index 551b8167..aa4718b6 100644 --- a/session_update.go +++ b/session_update.go @@ -335,9 +335,9 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var top string if st.LimitN != nil { limitValue := *st.LimitN - if session.engine.dialect.DBType() == schemas.MYSQL { + if session.engine.dialect.URI().DBType == schemas.MYSQL { condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) - } else if session.engine.dialect.DBType() == schemas.SQLITE { + } else if session.engine.dialect.URI().DBType == schemas.SQLITE { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -348,7 +348,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if session.engine.dialect.DBType() == schemas.POSTGRES { + } else if session.engine.dialect.URI().DBType == schemas.POSTGRES { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -360,8 +360,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if session.engine.dialect.DBType() == schemas.MSSQL { - if st.OrderStr != "" && session.engine.dialect.DBType() == schemas.MSSQL && + } else if session.engine.dialect.URI().DBType == schemas.MSSQL { + if st.OrderStr != "" && session.engine.dialect.URI().DBType == schemas.MSSQL && 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], @@ -387,7 +387,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var tableAlias = session.engine.Quote(tableName) var fromSQL string if session.statement.TableAlias != "" { - switch session.engine.dialect.DBType() { + switch session.engine.dialect.URI().DBType { case schemas.MSSQL: fromSQL = fmt.Sprintf("FROM %s %s ", tableAlias, session.statement.TableAlias) tableAlias = session.statement.TableAlias diff --git a/tags_test.go b/tags_test.go index 4473a12f..ff578def 100644 --- a/tags_test.go +++ b/tags_test.go @@ -238,7 +238,7 @@ func TestExtends2(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -248,7 +248,7 @@ func TestExtends2(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -299,7 +299,7 @@ func TestExtends3(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -308,7 +308,7 @@ func TestExtends3(t *testing.T) { _, err = session.Insert(&msg) assert.NoError(t, err) - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -362,7 +362,7 @@ func TestExtends4(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -371,7 +371,7 @@ func TestExtends4(t *testing.T) { _, err = session.Insert(&msg) assert.NoError(t, err) - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -800,7 +800,7 @@ func TestAutoIncrTag(t *testing.T) { func TestTagComment(t *testing.T) { assert.NoError(t, prepareEngine()) // FIXME: only support mysql - if testEngine.Dialect().DBType() != schemas.MYSQL { + if testEngine.Dialect().URI().DBType != schemas.MYSQL { return } diff --git a/types_test.go b/types_test.go index d8fd8309..77407e98 100644 --- a/types_test.go +++ b/types_test.go @@ -314,7 +314,7 @@ func TestCustomType2(t *testing.T) { session := testEngine.NewSession() defer session.Close() - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("set IDENTITY_INSERT " + tableName + " on") @@ -325,7 +325,7 @@ func TestCustomType2(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == schemas.MSSQL { + if testEngine.Dialect().URI().DBType == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } From ccf65397e8d5a5e1b4c062de93b14d60a9b0e090 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 7 Mar 2020 10:00:05 +0000 Subject: [PATCH 056/112] Improve dialect interface (#1579) Fix bug Improve dialect interface Reviewed-on: https://gitea.com/xorm/xorm/pulls/1579 --- dialects/dialect.go | 16 +++++----------- dialects/mssql.go | 17 ++++++---------- dialects/mysql.go | 13 ++++--------- dialects/oracle.go | 33 +++++++------------------------- dialects/postgres.go | 17 ++++++---------- dialects/sqlite3.go | 13 ++++--------- engine.go | 11 ++--------- internal/statements/statement.go | 3 ++- session_insert.go | 18 ++++------------- session_schema.go | 16 ++++++---------- 10 files changed, 46 insertions(+), 111 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index b074d485..806d8949 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -43,8 +43,6 @@ type Dialect interface { SetQuotePolicy(quotePolicy QuotePolicy) AutoIncrStr() string - SupportInsertMany() bool - SupportDropIfExists() bool GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) IndexCheckSQL(tableName, idxName string) (string, []interface{}) @@ -52,9 +50,9 @@ type Dialect interface { DropIndexSQL(tableName string, index *schemas.Index) string GetTables(ctx context.Context) ([]*schemas.Table, error) - TableCheckSQL(tableName string) (string, []interface{}) - CreateTableSQL(table *schemas.Table, tableName string) string - DropTableSQL(tableName string) string + IsTableExist(ctx context.Context, tableName string) (bool, error) + CreateTableSQL(table *schemas.Table, tableName string) (string, bool) + DropTableSQL(tableName string) (string, bool) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) IsColumnExist(ctx context.Context, tableName string, colName string) (bool, error) @@ -149,13 +147,9 @@ func (b *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } -func (db *Base) SupportDropIfExists() bool { - return true -} - -func (db *Base) DropTableSQL(tableName string) string { +func (db *Base) DropTableSQL(tableName string) (string, bool) { quote := db.dialect.Quoter().Quote - return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)) + return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)), true } func (db *Base) HasRecords(ctx context.Context, query string, args ...interface{}) (bool, error) { diff --git a/dialects/mssql.go b/dialects/mssql.go index 06ab0b78..0d857f32 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -281,10 +281,6 @@ func (db *mssql) SQLType(c *schemas.Column) string { return res } -func (db *mssql) SupportInsertMany() bool { - return true -} - func (db *mssql) IsReserved(name string) bool { _, ok := mssqlReservedWords[strings.ToUpper(name)] return ok @@ -311,10 +307,10 @@ func (db *mssql) AutoIncrStr() string { return "IDENTITY" } -func (db *mssql) DropTableSQL(tableName string) string { +func (db *mssql) DropTableSQL(tableName string) (string, bool) { return fmt.Sprintf("IF EXISTS (SELECT * FROM sysobjects WHERE id = "+ "object_id(N'%s') and OBJECTPROPERTY(id, N'IsUserTable') = 1) "+ - "DROP TABLE \"%s\"", tableName, tableName) + "DROP TABLE \"%s\"", tableName, tableName), true } func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { @@ -329,10 +325,9 @@ func (db *mssql) IsColumnExist(ctx context.Context, tableName, colName string) ( return db.HasRecords(ctx, query, tableName, colName) } -func (db *mssql) TableCheckSQL(tableName string) (string, []interface{}) { - args := []interface{}{} +func (db *mssql) IsTableExist(ctx context.Context, tableName string) (bool, error) { sql := "select * from sysobjects where id = object_id(N'" + tableName + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1" - return sql, args + return db.HasRecords(ctx, sql) } func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { @@ -491,7 +486,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 { +func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { var sql string if tableName == "" { tableName = table.Name @@ -522,7 +517,7 @@ func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) string { sql = sql[:len(sql)-2] + ")" sql += ";" - return sql + return sql, true } func (db *mssql) ForUpdateSQL(query string) string { diff --git a/dialects/mysql.go b/dialects/mysql.go index 364f22b6..3c8d3c2a 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -270,10 +270,6 @@ func (db *mysql) SQLType(c *schemas.Column) string { return res } -func (db *mysql) SupportInsertMany() bool { - return true -} - func (db *mysql) IsReserved(name string) bool { _, ok := mysqlReservedWords[strings.ToUpper(name)] return ok @@ -290,10 +286,9 @@ func (db *mysql) IndexCheckSQL(tableName, idxName string) (string, []interface{} return sql, args } -func (db *mysql) TableCheckSQL(tableName string) (string, []interface{}) { - args := []interface{}{db.uri.DBName, tableName} +func (db *mysql) IsTableExist(ctx context.Context, tableName string) (bool, error) { sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" - return sql, args + return db.HasRecords(ctx, sql, db.uri.DBName, tableName) } func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { @@ -512,7 +507,7 @@ func (db *mysql) GetIndexes(ctx context.Context, tableName string) (map[string]* return indexes, nil } -func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) string { +func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { var sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { tableName = table.Name @@ -565,7 +560,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) string { if db.rowFormat != "" { sql += " ROW_FORMAT=" + db.rowFormat } - return sql + return sql, true } func (db *mysql) Filters() []Filter { diff --git a/dialects/oracle.go b/dialects/oracle.go index e0d83115..fd2f0fd1 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -547,24 +547,16 @@ func (db *oracle) AutoIncrStr() string { return "AUTO_INCREMENT" } -func (db *oracle) SupportInsertMany() bool { - return true -} - func (db *oracle) IsReserved(name string) bool { _, ok := oracleReservedWords[strings.ToUpper(name)] return ok } -func (db *oracle) SupportDropIfExists() bool { - return false +func (db *oracle) DropTableSQL(tableName string) (string, bool) { + return fmt.Sprintf("DROP TABLE `%s`", tableName), false } -func (db *oracle) DropTableSQL(tableName string) string { - return fmt.Sprintf("DROP TABLE `%s`", tableName) -} - -func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) string { +func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { var sql = "CREATE TABLE " if tableName == "" { tableName = table.Name @@ -593,7 +585,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) string } sql = sql[:len(sql)-2] + ")" - return sql + return sql, false } func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { @@ -619,26 +611,15 @@ func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{ `WHERE TABLE_NAME = :1 AND INDEX_NAME = :2`, args } -func (db *oracle) TableCheckSQL(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return `SELECT table_name FROM user_tables WHERE table_name = :1`, args +func (db *oracle) IsTableExist(ctx context.Context, tableName string) (bool, error) { + return db.HasRecords(ctx, `SELECT table_name FROM user_tables WHERE table_name = :1`, tableName) } func (db *oracle) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { args := []interface{}{tableName, colName} query := "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = :1" + " AND column_name = :2" - - rows, err := db.DB().QueryContext(ctx, query, args...) - if err != nil { - return false, err - } - defer rows.Close() - - if rows.Next() { - return true, nil - } - return false, nil + return db.HasRecords(ctx, query, args...) } func (db *oracle) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { diff --git a/dialects/postgres.go b/dialects/postgres.go index 31cd49b6..69100627 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -884,10 +884,6 @@ func (db *postgres) SQLType(c *schemas.Column) string { return res } -func (db *postgres) SupportInsertMany() bool { - return true -} - func (db *postgres) IsReserved(name string) bool { _, ok := postgresReservedWords[strings.ToUpper(name)] return ok @@ -897,7 +893,7 @@ func (db *postgres) AutoIncrStr() string { return "" } -func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) string { +func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { var sql string sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { @@ -932,7 +928,7 @@ func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) strin } sql += ")" - return sql + return sql, true } func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { @@ -946,14 +942,13 @@ func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interfac `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } -func (db *postgres) TableCheckSQL(tableName string) (string, []interface{}) { +func (db *postgres) IsTableExist(ctx context.Context, tableName string) (bool, error) { if len(db.uri.Schema) == 0 { - args := []interface{}{tableName} - return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args + return db.HasRecords(ctx, `SELECT tablename FROM pg_tables WHERE tablename = $1`, tableName) } - args := []interface{}{db.uri.Schema, tableName} - return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args + return db.HasRecords(ctx, `SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = $2`, + db.uri.Schema, tableName) } func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 212c5a8e..4bd3147d 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -211,10 +211,6 @@ func (db *sqlite3) FormatBytes(bs []byte) string { return fmt.Sprintf("X'%x'", bs) } -func (db *sqlite3) SupportInsertMany() bool { - return true -} - func (db *sqlite3) IsReserved(name string) bool { _, ok := sqlite3ReservedWords[strings.ToUpper(name)] return ok @@ -229,9 +225,8 @@ func (db *sqlite3) IndexCheckSQL(tableName, idxName string) (string, []interface return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args } -func (db *sqlite3) TableCheckSQL(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +func (db *sqlite3) IsTableExist(ctx context.Context, tableName string) (bool, error) { + return db.HasRecords(ctx, "SELECT name FROM sqlite_master WHERE type='table' and name = ?", tableName) } func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { @@ -249,7 +244,7 @@ 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 { +func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { var sql string sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { @@ -284,7 +279,7 @@ func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) string } sql += ")" - return sql + return sql, true } func (db *sqlite3) ForUpdateSQL(query string) string { diff --git a/engine.go b/engine.go index c330e9f5..8d77aeef 100644 --- a/engine.go +++ b/engine.go @@ -125,14 +125,6 @@ func (engine *Engine) SetColumnMapper(mapper names.Mapper) { engine.tagParser.SetColumnMapper(mapper) } -// SupportInsertMany If engine's database support batch insert records like -// "insert into user values (name, age), (name, age)". -// When the return is ture, then engine.Insert(&users) will -// generate batch sql and exeute. -func (engine *Engine) SupportInsertMany() bool { - return engine.dialect.SupportInsertMany() -} - // Quote Use QuoteStr quote the string sql func (engine *Engine) Quote(value string) string { value = strings.TrimSpace(value) @@ -388,7 +380,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } - _, err = io.WriteString(w, dialect.CreateTableSQL(table, "")+";\n") + s, _ := dialect.CreateTableSQL(table, "") + _, err = io.WriteString(w, s+";\n") if err != nil { return err } diff --git a/internal/statements/statement.go b/internal/statements/statement.go index d6dd58b1..0b670da5 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -643,7 +643,8 @@ func (statement *Statement) genColumnStr() string { func (statement *Statement) GenCreateTableSQL() string { statement.RefTable.StoreEngine = statement.StoreEngine statement.RefTable.Charset = statement.Charset - return statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName()) + s, _ := statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName()) + return s } func (statement *Statement) GenIndexSQL() []string { diff --git a/session_insert.go b/session_insert.go index e5368571..91257f0a 100644 --- a/session_insert.go +++ b/session_insert.go @@ -75,21 +75,11 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) { return 0, ErrNoElementsOnSlice } - if session.engine.SupportInsertMany() { - cnt, err := session.innerInsertMulti(bean) - if err != nil { - return affected, err - } - affected += cnt - } else { - for i := 0; i < size; i++ { - cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) - if err != nil { - return affected, err - } - affected += cnt - } + cnt, err := session.innerInsertMulti(bean) + if err != nil { + return affected, err } + affected += cnt } else { cnt, err := session.innerInsert(bean) if err != nil { diff --git a/session_schema.go b/session_schema.go index 6d363521..047be23d 100644 --- a/session_schema.go +++ b/session_schema.go @@ -124,18 +124,16 @@ func (session *Session) DropTable(beanOrTableName interface{}) error { func (session *Session) dropTable(beanOrTableName interface{}) error { tableName := session.engine.TableName(beanOrTableName) - var needDrop = true - if !session.engine.dialect.SupportDropIfExists() { - sqlStr, args := session.engine.dialect.TableCheckSQL(tableName) - results, err := session.queryBytes(sqlStr, args...) + sqlStr, checkIfExist := session.engine.dialect.DropTableSQL(session.engine.TableName(tableName, true)) + if !checkIfExist { + exist, err := session.engine.dialect.IsTableExist(session.ctx, tableName) if err != nil { return err } - needDrop = len(results) > 0 + checkIfExist = exist } - if needDrop { - sqlStr := session.engine.Dialect().DropTableSQL(session.engine.TableName(tableName, true)) + if checkIfExist { _, err := session.exec(sqlStr) return err } @@ -154,9 +152,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) } func (session *Session) isTableExist(tableName string) (bool, error) { - sqlStr, args := session.engine.dialect.TableCheckSQL(tableName) - results, err := session.queryBytes(sqlStr, args...) - return len(results) > 0, err + return session.engine.dialect.IsTableExist(session.ctx, tableName) } // IsTableEmpty if table have any records From 257653726e1044a646ed05dff29430387628a231 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 7 Mar 2020 12:06:28 +0000 Subject: [PATCH 057/112] return sqls for create table (#1580) return sqls for create table Reviewed-on: https://gitea.com/xorm/xorm/pulls/1580 --- dialects/dialect.go | 2 +- dialects/mssql.go | 4 ++-- dialects/mysql.go | 4 ++-- dialects/oracle.go | 4 ++-- dialects/postgres.go | 4 ++-- dialects/sqlite3.go | 4 ++-- engine.go | 10 ++++++---- internal/statements/statement.go | 2 +- session_schema.go | 11 ++++++++--- 9 files changed, 26 insertions(+), 19 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 806d8949..35139817 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -51,7 +51,7 @@ type Dialect interface { GetTables(ctx context.Context) ([]*schemas.Table, error) IsTableExist(ctx context.Context, tableName string) (bool, error) - CreateTableSQL(table *schemas.Table, tableName string) (string, bool) + CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) DropTableSQL(tableName string) (string, bool) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) diff --git a/dialects/mssql.go b/dialects/mssql.go index 0d857f32..6ba2cd97 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -486,7 +486,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(table *schemas.Table, tableName string) ([]string, bool) { var sql string if tableName == "" { tableName = table.Name @@ -517,7 +517,7 @@ func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) (string, sql = sql[:len(sql)-2] + ")" sql += ";" - return sql, true + return []string{sql}, true } func (db *mssql) ForUpdateSQL(query string) string { diff --git a/dialects/mysql.go b/dialects/mysql.go index 3c8d3c2a..78acf1d0 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -507,7 +507,7 @@ func (db *mysql) GetIndexes(ctx context.Context, tableName string) (map[string]* return indexes, nil } -func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { +func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { var sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { tableName = table.Name @@ -560,7 +560,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) (string, if db.rowFormat != "" { sql += " ROW_FORMAT=" + db.rowFormat } - return sql, true + return []string{sql}, true } func (db *mysql) Filters() []Filter { diff --git a/dialects/oracle.go b/dialects/oracle.go index fd2f0fd1..045ad99b 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -556,7 +556,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(table *schemas.Table, tableName string) ([]string, bool) { var sql = "CREATE TABLE " if tableName == "" { tableName = table.Name @@ -585,7 +585,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) (string } sql = sql[:len(sql)-2] + ")" - return sql, false + return []string{sql}, false } func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { diff --git a/dialects/postgres.go b/dialects/postgres.go index 69100627..e393452f 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -893,7 +893,7 @@ func (db *postgres) AutoIncrStr() string { return "" } -func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) (string, bool) { +func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { var sql string sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { @@ -928,7 +928,7 @@ func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) (stri } sql += ")" - return sql, true + return []string{sql}, true } func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 4bd3147d..710babe6 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -244,7 +244,7 @@ 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) { +func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { var sql string sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { @@ -279,7 +279,7 @@ func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) (strin } sql += ")" - return sql, true + return []string{sql}, true } func (db *sqlite3) ForUpdateSQL(query string) string { diff --git a/engine.go b/engine.go index 8d77aeef..d94591e1 100644 --- a/engine.go +++ b/engine.go @@ -380,10 +380,12 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } - s, _ := dialect.CreateTableSQL(table, "") - _, err = io.WriteString(w, s+";\n") - if err != nil { - return err + sqls, _ := dialect.CreateTableSQL(table, "") + for _, s := range sqls { + _, err = io.WriteString(w, s+";\n") + if err != nil { + return err + } } for _, index := range table.Indexes { _, err = io.WriteString(w, dialect.CreateIndexSQL(table.Name, index)+";\n") diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 0b670da5..e8675443 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -640,7 +640,7 @@ func (statement *Statement) genColumnStr() string { return buf.String() } -func (statement *Statement) GenCreateTableSQL() string { +func (statement *Statement) GenCreateTableSQL() []string { statement.RefTable.StoreEngine = statement.StoreEngine statement.RefTable.Charset = statement.Charset s, _ := statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName()) diff --git a/session_schema.go b/session_schema.go index 047be23d..ca4e2d75 100644 --- a/session_schema.go +++ b/session_schema.go @@ -37,9 +37,14 @@ func (session *Session) createTable(bean interface{}) error { return err } - sqlStr := session.statement.GenCreateTableSQL() - _, err := session.exec(sqlStr) - return err + sqlStrs := session.statement.GenCreateTableSQL() + for _, s := range sqlStrs { + _, err := session.exec(s) + if err != nil { + return err + } + } + return nil } // CreateIndexes create indexes From 8b100bb7d46878b2238bd47b477156ca7bc91860 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Mar 2020 02:29:25 +0000 Subject: [PATCH 058/112] Fix find alias bug (#1581) Code improvement Fix find alias bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1581 --- internal/statements/statement.go | 14 +++++++++----- session_find.go | 21 ++++++--------------- session_find_test.go | 10 ++++++++++ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index e8675443..f8cb6f2d 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -35,9 +35,8 @@ var ( // Statement save all the sql info for executing SQL type Statement struct { - RefTable *schemas.Table - dialect dialects.Dialect - //Engine *Engine + RefTable *schemas.Table + dialect dialects.Dialect defaultTimeZone *time.Location tagParser *tags.Parser Start int @@ -985,8 +984,13 @@ func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { var colName = col.Name if statement.JoinStr != "" { - colName = statement.quote(statement.TableName()) + - "." + statement.quote(col.Name) + var prefix string + if statement.TableAlias != "" { + prefix = statement.TableAlias + } else { + prefix = statement.TableName() + } + colName = statement.quote(prefix) + "." + statement.quote(col.Name) } var cond = builder.NewCond() if col.SQLType.IsNumeric() { diff --git a/session_find.go b/session_find.go index 72882a28..960c1085 100644 --- a/session_find.go +++ b/session_find.go @@ -99,10 +99,11 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } } - var table = session.statement.RefTable - - var addedTableName = (len(session.statement.JoinStr) > 0) - var autoCond builder.Cond + var ( + table = session.statement.RefTable + addedTableName = (len(session.statement.JoinStr) > 0) + autoCond builder.Cond + ) if tp == tpStruct { if !session.statement.NoAutoCondition && len(condiBean) > 0 { var err error @@ -111,23 +112,13 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) return err } } else { - // !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) } } } + // if it's a map with Cols but primary key not in column list, we still need the primary key if isMap && !session.statement.ColumnMap.IsEmpty() { for _, k := range session.statement.RefTable.PrimaryKeys { session.statement.ColumnMap.Add(k) diff --git a/session_find_test.go b/session_find_test.go index 28ba1d60..c16d0a1b 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -794,6 +794,16 @@ func TestMoreExtends(t *testing.T) { Limit(10, 10). Find(&books) assert.NoError(t, err) + + 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"). + Limit(10, 10). + Find(&books) + assert.NoError(t, err) } func TestDistinctAndCols(t *testing.T) { From f238bb9d0772071d2ecf1f7d445bfe7d7bbc66db Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Mar 2020 05:14:28 +0000 Subject: [PATCH 059/112] Improve insert (#1583) Improve insert Reviewed-on: https://gitea.com/xorm/xorm/pulls/1583 --- error.go | 11 --- internal/statements/insert.go | 143 +++++++++++++++++++++++++++++++ session.go | 10 +++ session_insert.go | 157 ++++++++-------------------------- 4 files changed, 190 insertions(+), 131 deletions(-) create mode 100644 internal/statements/insert.go diff --git a/error.go b/error.go index 21a83f47..3708ce4f 100644 --- a/error.go +++ b/error.go @@ -6,7 +6,6 @@ package xorm import ( "errors" - "fmt" ) var ( @@ -23,13 +22,3 @@ var ( // ErrConditionType condition type unsupported ErrConditionType = errors.New("Unsupported condition type") ) - -// ErrFieldIsNotExist columns does not exist -type ErrFieldIsNotExist struct { - FieldName string - TableName string -} - -func (e ErrFieldIsNotExist) Error() string { - return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) -} diff --git a/internal/statements/insert.go b/internal/statements/insert.go new file mode 100644 index 00000000..db2fc91c --- /dev/null +++ b/internal/statements/insert.go @@ -0,0 +1,143 @@ +// Copyright 2020 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 ( + "strings" + + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +func (statement *Statement) writeInsertOutput(buf *strings.Builder, table *schemas.Table) error { + if statement.dialect.URI().DBType == schemas.MSSQL && len(table.AutoIncrement) > 0 { + if _, err := buf.WriteString(" OUTPUT Inserted."); err != nil { + return err + } + if _, err := buf.WriteString(table.AutoIncrement); err != nil { + return err + } + } + return nil +} + +func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) (string, []interface{}, error) { + var ( + table = statement.RefTable + tableName = statement.TableName() + exprs = statement.ExprColumns + colPlaces = strings.Repeat("?, ", len(colNames)) + ) + if exprs.Len() <= 0 && len(colPlaces) > 0 { + colPlaces = colPlaces[0 : len(colPlaces)-2] + } + + var buf = builder.NewWriter() + if _, err := buf.WriteString("INSERT INTO "); err != nil { + return "", nil, err + } + + if err := statement.dialect.Quoter().QuoteTo(buf.Builder, tableName); err != nil { + return "", nil, err + } + + if len(colPlaces) <= 0 { + if statement.dialect.URI().DBType == schemas.MYSQL { + if _, err := buf.WriteString(" VALUES ()"); err != nil { + return "", nil, err + } + } else { + if err := statement.writeInsertOutput(buf.Builder, table); err != nil { + return "", nil, err + } + if _, err := buf.WriteString(" DEFAULT VALUES"); err != nil { + return "", nil, err + } + } + } else { + if _, err := buf.WriteString(" ("); err != nil { + return "", nil, err + } + + if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames...), ","); err != nil { + return "", nil, err + } + + if statement.Conds().IsValid() { + if _, err := buf.WriteString(")"); err != nil { + return "", nil, err + } + if err := statement.writeInsertOutput(buf.Builder, table); err != nil { + return "", nil, err + } + if _, err := buf.WriteString(" SELECT "); err != nil { + return "", nil, err + } + + if err := statement.WriteArgs(buf, args); err != nil { + return "", nil, err + } + + if len(exprs.Args) > 0 { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + } + if err := exprs.WriteArgs(buf); err != nil { + return "", nil, err + } + + if _, err := buf.WriteString(" FROM "); err != nil { + return "", nil, err + } + + if err := statement.dialect.Quoter().QuoteTo(buf.Builder, tableName); err != nil { + return "", nil, err + } + + if _, err := buf.WriteString(" WHERE "); err != nil { + return "", nil, err + } + + if err := statement.Conds().WriteTo(buf); err != nil { + return "", nil, err + } + } else { + buf.Append(args...) + + if _, err := buf.WriteString(")"); err != nil { + return "", nil, err + } + if err := statement.writeInsertOutput(buf.Builder, table); err != nil { + return "", nil, err + } + if _, err := buf.WriteString(" VALUES ("); err != nil { + return "", nil, err + } + if _, err := buf.WriteString(colPlaces); 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 len(table.AutoIncrement) > 0 && statement.dialect.URI().DBType == schemas.POSTGRES { + if _, err := buf.WriteString(" RETURNING "); err != nil { + return "", nil, err + } + if err := statement.dialect.Quoter().QuoteTo(buf.Builder, table.AutoIncrement); err != nil { + return "", nil, err + } + } + + return buf.String(), buf.Args(), nil +} diff --git a/session.go b/session.go index 07b99594..4842883b 100644 --- a/session.go +++ b/session.go @@ -22,6 +22,16 @@ import ( "xorm.io/xorm/schemas" ) +// ErrFieldIsNotExist columns does not exist +type ErrFieldIsNotExist struct { + FieldName string + TableName string +} + +func (e ErrFieldIsNotExist) Error() string { + return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) +} + // ErrFieldIsNotValid is not valid type ErrFieldIsNotValid struct { FieldName string diff --git a/session_insert.go b/session_insert.go index 91257f0a..b2e92309 100644 --- a/session_insert.go +++ b/session_insert.go @@ -326,8 +326,6 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 0, ErrTableNotFound } - table := session.statement.RefTable - // handle BeforeInsertProcessor for _, closure := range session.beforeClosures { closure(bean) @@ -338,100 +336,19 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { processor.BeforeInsert() } + var tableName = session.statement.TableName() + table := session.statement.RefTable + colNames, args, err := session.genInsertColumns(bean) if err != nil { return 0, err } - exprs := session.statement.ExprColumns - colPlaces := strings.Repeat("?, ", len(colNames)) - if exprs.Len() <= 0 && len(colPlaces) > 0 { - colPlaces = colPlaces[0 : len(colPlaces)-2] - } - - var tableName = session.statement.TableName() - var output string - if session.engine.dialect.URI().DBType == schemas.MSSQL && len(table.AutoIncrement) > 0 { - output = fmt.Sprintf(" OUTPUT Inserted.%s", table.AutoIncrement) - } - - var buf = builder.NewWriter() - if _, err := buf.WriteString(fmt.Sprintf("INSERT INTO %s", session.engine.Quote(tableName))); err != nil { + sqlStr, args, err := session.statement.GenInsertSQL(colNames, args) + if err != nil { return 0, err } - if len(colPlaces) <= 0 { - if session.engine.dialect.URI().DBType == schemas.MYSQL { - if _, err := buf.WriteString(" VALUES ()"); err != nil { - return 0, err - } - } else { - if _, err := buf.WriteString(fmt.Sprintf("%s DEFAULT VALUES", output)); err != nil { - return 0, err - } - } - } else { - if _, err := buf.WriteString(" ("); err != nil { - return 0, err - } - - if err := session.engine.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames...), ","); err != nil { - return 0, err - } - - if session.statement.Conds().IsValid() { - if _, err := buf.WriteString(fmt.Sprintf(")%s SELECT ", output)); err != nil { - return 0, err - } - - if err := session.statement.WriteArgs(buf, args); err != nil { - return 0, err - } - - if len(exprs.Args) > 0 { - if _, err := buf.WriteString(","); err != nil { - return 0, err - } - } - if err := exprs.WriteArgs(buf); err != nil { - return 0, err - } - - if _, err := buf.WriteString(fmt.Sprintf(" FROM %v WHERE ", session.engine.Quote(tableName))); err != nil { - return 0, err - } - - if err := session.statement.Conds().WriteTo(buf); err != nil { - return 0, err - } - } else { - buf.Append(args...) - - if _, err := buf.WriteString(fmt.Sprintf(")%s VALUES (%v", - output, - colPlaces)); err != nil { - return 0, err - } - - if err := exprs.WriteArgs(buf); err != nil { - return 0, err - } - - if _, err := buf.WriteString(")"); err != nil { - return 0, err - } - } - } - - if len(table.AutoIncrement) > 0 && session.engine.dialect.URI().DBType == schemas.POSTGRES { - if _, err := buf.WriteString(" RETURNING " + session.engine.Quote(table.AutoIncrement)); err != nil { - return 0, err - } - } - - sqlStr := buf.String() - args = buf.Args() - handleAfterInsertProcessorFunc := func(bean interface{}) { if session.isAutoCommit { for _, closure := range session.afterClosures { @@ -545,48 +462,48 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue.Set(int64ToIntValue(id, aiValue.Type())) return 1, nil - } else { - res, err := session.exec(sqlStr, args...) - if err != nil { - return 0, err - } + } - defer handleAfterInsertProcessorFunc(bean) + res, err := session.exec(sqlStr, args...) + if err != nil { + return 0, err + } - session.cacheInsert(tableName) + defer handleAfterInsertProcessorFunc(bean) - 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) - } - } + session.cacheInsert(tableName) - if table.AutoIncrement == "" { - return res.RowsAffected() - } - - var id int64 - id, err = res.LastInsertId() - if err != nil || id <= 0 { - return res.RowsAffected() - } - - aiValue, err := table.AutoIncrColumn().ValueOf(bean) + 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) } + } - if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { - return res.RowsAffected() - } - - aiValue.Set(int64ToIntValue(id, aiValue.Type())) - + if table.AutoIncrement == "" { return res.RowsAffected() } + + var id int64 + id, err = res.LastInsertId() + if err != nil || id <= 0 { + return res.RowsAffected() + } + + aiValue, err := table.AutoIncrColumn().ValueOf(bean) + if err != nil { + session.engine.logger.Errorf("%v", err) + } + + if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { + return res.RowsAffected() + } + + aiValue.Set(int64ToIntValue(id, aiValue.Type())) + + return res.RowsAffected() } // InsertOne insert only one struct into database as a record. From 00b65c6d99e39166238cc9bbc1d654b75be85c51 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Mar 2020 06:10:05 +0000 Subject: [PATCH 060/112] Update README (#1582) Remove version Update README Reviewed-on: https://gitea.com/xorm/xorm/pulls/1582 --- README.md | 22 ++++++++++++---------- README_CN.md | 34 +++++++++++++++++----------------- engine.go | 4 ++-- xorm.go | 7 +------ 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index de4ba2f8..05a15cef 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Xorm is a simple and powerful ORM for Go. * Query Cache speed up -* Database Reverse support, See [Xorm Tool README](https://github.com/go-xorm/cmd/blob/master/README.md) +* Database Reverse support via [xorm.io/reverse](https://xorm.io/reverse) * Simple cascade loading support @@ -40,19 +40,21 @@ Xorm is a simple and powerful ORM for Go. Drivers for Go's sql package which currently support database/sql includes: -* Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) +* [Mysql5.*](https://github.com/mysql/mysql-server/tree/5.7) / [Mysql8.*](https://github.com/mysql/mysql-server) / [Mariadb](https://github.com/MariaDB/server) / [Tidb](https://github.com/pingcap/tidb) + - [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) + - [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) -* MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/tree/master/godrv) +* [Postgres](https://github.com/postgres/postgres) / [Cockroach](https://github.com/cockroachdb/cockroach) + - [github.com/lib/pq](https://github.com/lib/pq) -* Postgres: [github.com/lib/pq](https://github.com/lib/pq) +* [SQLite](https://sqlite.org) + - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) -* Tidb: [github.com/pingcap/tidb](https://github.com/pingcap/tidb) +* MsSql + - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) -* SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) - -* MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) - -* Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) +* Oracle + - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) ## Installation diff --git a/README_CN.md b/README_CN.md index 5aae4fcb..d9ee6d8d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,15 +2,15 @@ [English](https://gitea.com/xorm/xorm/src/branch/master/README.md) -xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 +xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 -[![Build Status](https://drone.gitea.com/api/badges/xorm/builder/status.svg)](https://drone.gitea.com/xorm/builder) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) +[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) ## 特性 -* 支持Struct和数据库表之间的灵活映射,并支持自动同步 +* 支持 Struct 和数据库表之间的灵活映射,并支持自动同步 * 事务支持 @@ -18,7 +18,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * 使用连写来简化调用 -* 支持使用Id, In, Where, Limit, Join, Having, Table, Sql, Cols等函数和结构体等方式作为条件 +* 支持使用ID, In, Where, Limit, Join, Having, Table, SQL, Cols等函数和结构体等方式作为条件 * 支持级联加载Struct @@ -26,11 +26,11 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * 支持缓存 -* 支持根据数据库自动生成xorm的结构体 +* 通过 [xorm.io/reverse](https://xorm.io/reverse) 支持根据数据库自动生成 xorm 结构体 * 支持记录版本(即乐观锁) -* 内置SQL Builder支持 +* 通过 [xorm.io/builder](https://xorm.io/builder) 内置 SQL Builder 支持 * 上下文缓存支持 @@ -38,21 +38,21 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 目前支持的Go数据库驱动和对应的数据库如下: -* Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) +* [Mysql5.*](https://github.com/mysql/mysql-server/tree/5.7) / [Mysql8.*](https://github.com/mysql/mysql-server) / [Mariadb](https://github.com/MariaDB/server) / [Tidb](https://github.com/pingcap/tidb) + - [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) + - [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) -* MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) +* [Postgres](https://github.com/postgres/postgres) / [Cockroach](https://github.com/cockroachdb/cockroach) + - [github.com/lib/pq](https://github.com/lib/pq) -* Postgres: [github.com/lib/pq](https://github.com/lib/pq) +* [SQLite](https://sqlite.org) + - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) -* Tidb: [github.com/pingcap/tidb](https://github.com/pingcap/tidb) +* MsSql + - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) -* SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) - -* MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) - -* MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc) - -* Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持) +* Oracle + - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持) ## 安装 diff --git a/engine.go b/engine.go index d94591e1..6dd4df3e 100644 --- a/engine.go +++ b/engine.go @@ -367,8 +367,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch distDBName = string(tp[0]) } - _, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm v%s %s, from %s to %s*/\n\n", - Version, time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.URI().DBType, strings.ToUpper(distDBName))) + _, 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, strings.ToUpper(distDBName))) if err != nil { return err } diff --git a/xorm.go b/xorm.go index 3618b718..2025522f 100644 --- a/xorm.go +++ b/xorm.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build go1.8 +// +build go1.11 package xorm @@ -20,11 +20,6 @@ import ( "xorm.io/xorm/tags" ) -const ( - // Version show the xorm's version - Version string = "0.8.0.1015" -) - func close(engine *Engine) { engine.Close() } From 3617ee736f86bc143c1667e81d037c54c7b4dacf Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Mar 2020 02:17:37 +0000 Subject: [PATCH 061/112] Only replace quotes when necessary (#1584) fix test improve code improve code improve code improve code Fix replace quote fix test Only replace quotes when necessary Reviewed-on: https://gitea.com/xorm/xorm/pulls/1584 --- dialects/filter.go | 44 -------------------------- dialects/filter_test.go | 29 ----------------- dialects/mssql.go | 2 +- dialects/oracle.go | 1 - dialects/postgres.go | 2 +- internal/statements/query.go | 18 ++++++----- internal/statements/statement.go | 53 ++++++++++++++++++++++++++------ rows.go | 2 +- schemas/quote.go | 38 +++++++++++++++++++++++ schemas/quote_test.go | 27 ++++++++++++++++ session_get.go | 2 +- session_raw.go | 3 +- session_update.go | 30 +++++++++--------- 13 files changed, 138 insertions(+), 113 deletions(-) diff --git a/dialects/filter.go b/dialects/filter.go index add8cc7d..6968b6ce 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -7,8 +7,6 @@ package dialects import ( "fmt" "strings" - - "xorm.io/xorm/schemas" ) // Filter is an interface to filter SQL @@ -16,48 +14,6 @@ type Filter interface { Do(sql string) string } -// QuoteFilter filter SQL replace ` to database's own quote character -type QuoteFilter struct { - quoter schemas.Quoter -} - -func (s *QuoteFilter) Do(sql string) string { - if s.quoter.IsEmpty() { - return sql - } - - var buf strings.Builder - buf.Grow(len(sql)) - - var beginSingleQuote bool - for i := 0; i < len(sql); i++ { - if !beginSingleQuote && sql[i] == '`' { - var j = i + 1 - for ; j < len(sql); j++ { - if sql[j] == '`' { - break - } - } - word := sql[i+1 : j] - isReserved := s.quoter.IsReserved(word) - if isReserved { - buf.WriteByte(s.quoter.Prefix) - } - buf.WriteString(word) - if isReserved { - buf.WriteByte(s.quoter.Suffix) - } - i = j - } else { - if sql[i] == '\'' { - beginSingleQuote = !beginSingleQuote - } - buf.WriteByte(sql[i]) - } - } - return buf.String() -} - // SeqFilter filter SQL replace ?, ? ... to $1, $2 ... type SeqFilter struct { Prefix string diff --git a/dialects/filter_test.go b/dialects/filter_test.go index e8395156..7e2ef0a2 100644 --- a/dialects/filter_test.go +++ b/dialects/filter_test.go @@ -3,38 +3,9 @@ package dialects import ( "testing" - "xorm.io/xorm/schemas" - "github.com/stretchr/testify/assert" ) -func TestQuoteFilter_Do(t *testing.T) { - f := QuoteFilter{schemas.Quoter{'[', ']', schemas.AlwaysReserve}} - var kases = []struct { - source string - expected string - }{ - { - "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?", - "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", - }, - { - "SELECT 'abc```test```''', `a` FROM b", - "SELECT 'abc```test```''', [a] FROM b", - }, - { - "UPDATE table SET `a` = ~ `a`, `b`='abc`'", - "UPDATE table SET [a] = ~ [a], [b]='abc`'", - }, - } - - for _, kase := range kases { - t.Run(kase.source, func(t *testing.T) { - assert.EqualValues(t, kase.expected, f.Do(kase.source)) - }) - } -} - func TestSeqFilter(t *testing.T) { var kases = map[string]string{ "SELECT * FROM TABLE1 WHERE a=? AND b=?": "SELECT * FROM TABLE1 WHERE a=$1 AND b=$2", diff --git a/dialects/mssql.go b/dialects/mssql.go index 6ba2cd97..cad18a29 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -525,7 +525,7 @@ func (db *mssql) ForUpdateSQL(query string) string { } func (db *mssql) Filters() []Filter { - return []Filter{&QuoteFilter{db.Quoter()}} + return []Filter{} } type odbcDriver struct { diff --git a/dialects/oracle.go b/dialects/oracle.go index 045ad99b..c48d32b9 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -793,7 +793,6 @@ func (db *oracle) GetIndexes(ctx context.Context, tableName string) (map[string] func (db *oracle) Filters() []Filter { return []Filter{ - &QuoteFilter{db.Quoter()}, &SeqFilter{Prefix: ":", Start: 1}, } } diff --git a/dialects/postgres.go b/dialects/postgres.go index e393452f..8412ad40 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1231,7 +1231,7 @@ func (db *postgres) GetIndexes(ctx context.Context, tableName string) (map[strin } func (db *postgres) Filters() []Filter { - return []Filter{&QuoteFilter{db.Quoter()}, &SeqFilter{Prefix: "$", Start: 1}} + return []Filter{&SeqFilter{Prefix: "$", Start: 1}} } type pqDriver struct { diff --git a/internal/statements/query.go b/internal/statements/query.go index 8f7aeebb..1568259e 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -16,11 +16,11 @@ import ( func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { if len(sqlOrArgs) > 0 { - return ConvertSQLOrArgs(sqlOrArgs...) + return statement.ConvertSQLOrArgs(sqlOrArgs...) } if statement.RawSQL != "" { - return statement.RawSQL, statement.RawParams, nil + return statement.GenRawSQL(), statement.RawParams, nil } if len(statement.TableName()) <= 0 { @@ -74,7 +74,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) { if statement.RawSQL != "" { - return statement.RawSQL, statement.RawParams, nil + return statement.GenRawSQL(), statement.RawParams, nil } statement.SetRefBean(bean) @@ -83,6 +83,8 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri for _, colName := range columns { if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { colName = statement.quote(colName) + } else { + colName = statement.ReplaceQuote(colName) } sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName)) } @@ -153,7 +155,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interface{}, error) { if statement.RawSQL != "" { - return statement.RawSQL, statement.RawParams, nil + return statement.GenRawSQL(), statement.RawParams, nil } var condArgs []interface{} @@ -193,7 +195,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB distinct = "DISTINCT " } - condSQL, condArgs, err := builder.ToSQL(statement.cond) + condSQL, condArgs, err := statement.GenCondSQL(statement.cond) if err != nil { return "", nil, err } @@ -313,7 +315,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interface{}, error) { if statement.RawSQL != "" { - return statement.RawSQL, statement.RawParams, nil + return statement.GenRawSQL(), statement.RawParams, nil } var sqlStr string @@ -332,7 +334,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac } if statement.Conds().IsValid() { - condSQL, condArgs, err := builder.ToSQL(statement.Conds()) + condSQL, condArgs, err := statement.GenCondSQL(statement.Conds()) if err != nil { return "", nil, err } @@ -382,7 +384,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interface{}, error) { if statement.RawSQL != "" { - return statement.RawSQL, statement.RawParams, nil + return statement.GenRawSQL(), statement.RawParams, nil } var sqlStr string diff --git a/internal/statements/statement.go b/internal/statements/statement.go index f8cb6f2d..4beb9a7e 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -98,6 +98,27 @@ 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) +} + +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 +} + +func (statement *Statement) ReplaceQuote(sql string) string { + if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL || + statement.dialect.URI().DBType == schemas.SQLITE { + return sql + } + return statement.dialect.Quoter().Replace(sql) +} + func (statement *Statement) SetContextCache(ctxCache contexts.ContextCache) { statement.Context = ctxCache } @@ -348,7 +369,11 @@ 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 { - statement.ExprColumns.addParam(column, expression) + if e, ok := expression.(string); ok { + statement.ExprColumns.addParam(column, statement.dialect.Quoter().Replace(e)) + } else { + statement.ExprColumns.addParam(column, expression) + } return statement } @@ -367,7 +392,7 @@ func (statement *Statement) ForUpdate() *Statement { // Select replace select func (statement *Statement) Select(str string) *Statement { - statement.SelectStr = str + statement.SelectStr = statement.ReplaceQuote(str) return statement } @@ -458,7 +483,7 @@ func (statement *Statement) OrderBy(order string) *Statement { if len(statement.OrderStr) > 0 { statement.OrderStr += ", " } - statement.OrderStr += order + statement.OrderStr += statement.ReplaceQuote(order) return statement } @@ -537,7 +562,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", subSQL, aliasName, condition) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), aliasName, statement.ReplaceQuote(condition)) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) case *builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() @@ -550,7 +575,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", subSQL, aliasName, condition) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), aliasName, statement.ReplaceQuote(condition)) statement.joinArgs = append(statement.joinArgs, subQueryArgs...) default: tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) @@ -559,7 +584,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition statement.dialect.Quoter().QuoteTo(&buf, tbName) tbName = buf.String() } - fmt.Fprintf(&buf, "%s ON %v", tbName, condition) + fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) } statement.JoinStr = buf.String() @@ -578,13 +603,13 @@ func (statement *Statement) tbNameNoSchema(table *schemas.Table) string { // GroupBy generate "Group By keys" statement func (statement *Statement) GroupBy(keys string) *Statement { - statement.GroupByStr = keys + statement.GroupByStr = statement.ReplaceQuote(keys) return statement } // Having generate "Having conditions" statement func (statement *Statement) Having(conditions string) *Statement { - statement.HavingStr = fmt.Sprintf("HAVING %v", conditions) + statement.HavingStr = fmt.Sprintf("HAVING %v", statement.ReplaceQuote(conditions)) return statement } @@ -926,7 +951,7 @@ func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, e return "", nil, err } - return builder.ToSQL(statement.cond) + return statement.GenCondSQL(statement.cond) } func (statement *Statement) quoteColumnStr(columnStr string) string { @@ -934,7 +959,15 @@ func (statement *Statement) quoteColumnStr(columnStr string) string { return statement.dialect.Quoter().Join(columns, ",") } -func ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { +func (statement *Statement) ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { + sql, args, err := convertSQLOrArgs(sqlOrArgs...) + if err != nil { + return "", nil, err + } + return statement.ReplaceQuote(sql), args, nil +} + +func convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { switch sqlOrArgs[0].(type) { case string: return sqlOrArgs[0].(string), sqlOrArgs[1:], nil diff --git a/rows.go b/rows.go index aa5e66e3..a56ea1c9 100644 --- a/rows.go +++ b/rows.go @@ -80,7 +80,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { return nil, err } } else { - sqlStr = rows.session.statement.RawSQL + sqlStr = rows.session.statement.GenRawSQL() args = rows.session.statement.RawParams } diff --git a/schemas/quote.go b/schemas/quote.go index 10436270..2a03152e 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -196,3 +196,41 @@ func (q Quoter) Strings(s []string) []string { } return res } + +// Replace replaces common quote(`) as the quotes on the sql +func (q Quoter) Replace(sql string) string { + if q.IsEmpty() { + return sql + } + + var buf strings.Builder + buf.Grow(len(sql)) + + var beginSingleQuote bool + for i := 0; i < len(sql); i++ { + if !beginSingleQuote && sql[i] == CommanQuoteMark { + var j = i + 1 + for ; j < len(sql); j++ { + if sql[j] == CommanQuoteMark { + break + } + } + word := sql[i+1 : j] + isReserved := q.IsReserved(word) + if isReserved { + buf.WriteByte(q.Prefix) + } + buf.WriteString(word) + if isReserved { + buf.WriteByte(q.Suffix) + } + i = j + } else { + if sql[i] == '\'' { + beginSingleQuote = !beginSingleQuote + } + buf.WriteByte(sql[i]) + } + } + return buf.String() +} diff --git a/schemas/quote_test.go b/schemas/quote_test.go index c7990f92..7a43bd24 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -146,3 +146,30 @@ func TestTrim(t *testing.T) { assert.EqualValues(t, dst, Quoter{'[', ']', AlwaysReserve}.Trim(src)) } } + +func TestReplace(t *testing.T) { + q := Quoter{'[', ']', AlwaysReserve} + var kases = []struct { + source string + expected string + }{ + { + "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?", + "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", + }, + { + "SELECT 'abc```test```''', `a` FROM b", + "SELECT 'abc```test```''', [a] FROM b", + }, + { + "UPDATE table SET `a` = ~ `a`, `b`='abc`'", + "UPDATE table SET [a] = ~ [a], [b]='abc`'", + }, + } + + for _, kase := range kases { + t.Run(kase.source, func(t *testing.T) { + assert.EqualValues(t, kase.expected, q.Replace(kase.source)) + }) + } +} diff --git a/session_get.go b/session_get.go index 76918194..e56ef2d7 100644 --- a/session_get.go +++ b/session_get.go @@ -59,7 +59,7 @@ func (session *Session) get(bean interface{}) (bool, error) { return false, err } } else { - sqlStr = session.statement.RawSQL + sqlStr = session.statement.GenRawSQL() args = session.statement.RawParams } diff --git a/session_raw.go b/session_raw.go index 0cea60b7..4cfe297a 100644 --- a/session_raw.go +++ b/session_raw.go @@ -9,7 +9,6 @@ import ( "reflect" "xorm.io/xorm/core" - "xorm.io/xorm/internal/statements" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { @@ -172,7 +171,7 @@ func (session *Session) Exec(sqlOrArgs ...interface{}) (sql.Result, error) { return nil, ErrUnSupportedType } - sqlStr, args, err := statements.ConvertSQLOrArgs(sqlOrArgs...) + sqlStr, args, err := session.statement.ConvertSQLOrArgs(sqlOrArgs...) if err != nil { return nil, err } diff --git a/session_update.go b/session_update.go index aa4718b6..f60f48e3 100644 --- a/session_update.go +++ b/session_update.go @@ -240,7 +240,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } colNames = append(colNames, session.engine.Quote(colName)+"="+tp) case *builder.Builder: - subQuery, subArgs, err := builder.ToSQL(tp) + subQuery, subArgs, err := session.statement.GenCondSQL(tp) if err != nil { return 0, err } @@ -317,7 +317,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - condSQL, condArgs, err = builder.ToSQL(cond) + if len(colNames) <= 0 { + return 0, errors.New("No content found to be updated") + } + + condSQL, condArgs, err = session.statement.GenCondSQL(cond) if err != nil { return 0, err } @@ -335,24 +339,25 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var top string if st.LimitN != nil { limitValue := *st.LimitN - if session.engine.dialect.URI().DBType == schemas.MYSQL { + switch session.engine.dialect.URI().DBType { + case schemas.MYSQL: condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) - } else if session.engine.dialect.URI().DBType == schemas.SQLITE { + case schemas.SQLITE: tempCondSQL := condSQL + fmt.Sprintf(" 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 = builder.ToSQL(cond) + condSQL, condArgs, err = session.statement.GenCondSQL(cond) if err != nil { return 0, err } if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if session.engine.dialect.URI().DBType == schemas.POSTGRES { + case schemas.POSTGRES: tempCondSQL := condSQL + fmt.Sprintf(" 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 = builder.ToSQL(cond) + condSQL, condArgs, err = session.statement.GenCondSQL(cond) if err != nil { return 0, err } @@ -360,14 +365,13 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if session.engine.dialect.URI().DBType == schemas.MSSQL { - if st.OrderStr != "" && session.engine.dialect.URI().DBType == schemas.MSSQL && - table != nil && len(table.PrimaryKeys) == 1 { + case schemas.MSSQL: + if st.OrderStr != "" && 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...) - condSQL, condArgs, err = builder.ToSQL(cond) + condSQL, condArgs, err = session.statement.GenCondSQL(cond) if err != nil { return 0, err } @@ -380,10 +384,6 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - if len(colNames) <= 0 { - return 0, errors.New("No content found to be updated") - } - var tableAlias = session.engine.Quote(tableName) var fromSQL string if session.statement.TableAlias != "" { From 4ef030c9ccb6a9b92b48af4777efef37f0391a16 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Mar 2020 06:37:03 +0000 Subject: [PATCH 062/112] =?UTF-8?q?mysql8.0=20=E4=B8=AD=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=89=8D=EF=BC=8C=E8=8E=B7=E5=8F=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E7=BB=93=E6=9E=84=EF=BC=8C=E5=9B=A0tableRaw?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E5=AF=BC=E8=87=B4=E5=90=8C=E6=AD=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=20(#808)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mysql8.0 中同步数据前,获取数据库结构,因tableRaw为空导致同步失败 Co-authored-by: 崔彦鹏 Reviewed-on: https://gitea.com/xorm/xorm/pulls/808 --- dialects/mysql.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dialects/mysql.go b/dialects/mysql.go index 78acf1d0..b7598680 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -413,7 +413,7 @@ func (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, ma func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{db.uri.DBName} - s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + + s := "SELECT `TABLE_NAME`, `ENGINE`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" rows, err := db.DB().QueryContext(ctx, s, args...) @@ -425,15 +425,17 @@ func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { tables := make([]*schemas.Table, 0) for rows.Next() { table := schemas.NewEmptyTable() - var name, engine, tableRows, comment string - var autoIncr *string - err = rows.Scan(&name, &engine, &tableRows, &autoIncr, &comment) + var name, engine string + var autoIncr, comment *string + err = rows.Scan(&name, &engine, &autoIncr, &comment) if err != nil { return nil, err } table.Name = name - table.Comment = comment + if comment != nil { + table.Comment = *comment + } table.StoreEngine = engine tables = append(tables, table) } From 188da20272dcc467e096cc08797b0f6a59150685 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Mar 2020 08:03:59 +0000 Subject: [PATCH 063/112] Move value2interface from session to statement package (#1587) Fix zero Fix tests Move value2interface from session to statement package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1587 --- internal/statements/values.go | 151 ++++++++++++++++++++++++++++++++++ internal/utils/zero.go | 50 +++++++++-- internal/utils/zero_test.go | 73 ++++++++++++++++ session_convert.go | 136 ------------------------------ session_insert.go | 27 ++---- session_update.go | 23 +----- 6 files changed, 275 insertions(+), 185 deletions(-) create mode 100644 internal/statements/values.go create mode 100644 internal/utils/zero_test.go diff --git a/internal/statements/values.go b/internal/statements/values.go new file mode 100644 index 00000000..b545a605 --- /dev/null +++ b/internal/statements/values.go @@ -0,0 +1,151 @@ +// 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 statements + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + "time" + + "xorm.io/xorm/convert" + "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/json" + "xorm.io/xorm/schemas" +) + +var ( + nullFloatType = reflect.TypeOf(sql.NullFloat64{}) +) + +// Value2Interface convert a field value of a struct to interface for puting 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 { + data, err := fieldConvert.ToDB() + if err != nil { + return nil, err + } + if col.SQLType.IsBlob() { + return data, nil + } + return string(data), nil + } + } + + if fieldConvert, ok := fieldValue.Interface().(convert.Conversion); ok { + data, err := fieldConvert.ToDB() + if err != nil { + return nil, err + } + if col.SQLType.IsBlob() { + return data, nil + } + return string(data), nil + } + + fieldType := fieldValue.Type() + k := fieldType.Kind() + if k == reflect.Ptr { + if fieldValue.IsNil() { + return nil, nil + } else if !fieldValue.IsValid() { + return nil, nil + } else { + // !nashtsai! deference pointer type to instance type + fieldValue = fieldValue.Elem() + fieldType = fieldValue.Type() + k = fieldType.Kind() + } + } + + switch k { + case reflect.Bool: + return fieldValue.Bool(), nil + case reflect.String: + return fieldValue.String(), nil + 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 + } else if fieldType.ConvertibleTo(nullFloatType) { + t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64) + if !t.Valid { + return nil, nil + } + return t.Float64, nil + } + + if !col.SQLType.IsJson() { + // !! 增加支持driver.Valuer接口的结构,如sql.NullString + if v, ok := fieldValue.Interface().(driver.Valuer); ok { + return v.Value() + } + + fieldTable, err := statement.tagParser.ParseWithCache(fieldValue) + if err != nil { + return nil, err + } + if len(fieldTable.PrimaryKeys) == 1 { + pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName) + return pkField.Interface(), nil + } + return nil, fmt.Errorf("no primary key for col %v", col.Name) + } + + if col.SQLType.IsText() { + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, err + } + return string(bytes), nil + } else if col.SQLType.IsBlob() { + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, err + } + return bytes, nil + } + return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type()) + case reflect.Complex64, reflect.Complex128: + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, err + } + return string(bytes), nil + case reflect.Array, reflect.Slice, reflect.Map: + if !fieldValue.IsValid() { + return fieldValue.Interface(), nil + } + + if col.SQLType.IsText() { + bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, err + } + return string(bytes), nil + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + if (k == reflect.Slice) && + (fieldValue.Type().Elem().Kind() == reflect.Uint8) { + bytes = fieldValue.Bytes() + } else { + bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) + if err != nil { + return nil, err + } + } + return bytes, nil + } + return nil, ErrUnSupportedType + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return int64(fieldValue.Uint()), nil + default: + return fieldValue.Interface(), nil + } +} diff --git a/internal/utils/zero.go b/internal/utils/zero.go index 5415fc15..8f033c60 100644 --- a/internal/utils/zero.go +++ b/internal/utils/zero.go @@ -13,7 +13,14 @@ type Zeroable interface { IsZero() bool } +var nilTime *time.Time + +// IsZero returns false if k is nil or has a zero value func IsZero(k interface{}) bool { + if k == nil { + return true + } + switch k.(type) { case int: return k.(int) == 0 @@ -43,28 +50,57 @@ func IsZero(k interface{}) bool { return k.(bool) == false case string: return k.(string) == "" + case *time.Time: + return k.(*time.Time) == nilTime || IsTimeZero(*k.(*time.Time)) + case time.Time: + return IsTimeZero(k.(time.Time)) case Zeroable: - return k.(Zeroable).IsZero() + return k.(Zeroable) == nil || k.(Zeroable).IsZero() + case reflect.Value: // for go version less than 1.13 because reflect.Value has no method IsZero + return IsValueZero(k.(reflect.Value)) } - return false + + return IsValueZero(reflect.ValueOf(k)) } +var zeroType = reflect.TypeOf((*Zeroable)(nil)).Elem() + func IsValueZero(v reflect.Value) bool { - if IsZero(v.Interface()) { - return true - } switch v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice: return v.IsNil() + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: + return v.Int() == 0 + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: + return v.Uint() == 0 + case reflect.String: + return v.Len() == 0 + case reflect.Ptr: + if v.IsNil() { + return true + } + return IsValueZero(v.Elem()) + case reflect.Struct: + return IsStructZero(v) + case reflect.Array: + return IsArrayZero(v) } return false } func IsStructZero(v reflect.Value) bool { - if !v.IsValid() { + if !v.IsValid() || v.NumField() == 0 { return true } + if v.Type().Implements(zeroType) { + f := v.MethodByName("IsZero") + if f.IsValid() { + res := f.Call(nil) + return len(res) == 1 && res[0].Bool() + } + } + for i := 0; i < v.NumField(); i++ { field := v.Field(i) switch field.Kind() { diff --git a/internal/utils/zero_test.go b/internal/utils/zero_test.go new file mode 100644 index 00000000..a5f4912a --- /dev/null +++ b/internal/utils/zero_test.go @@ -0,0 +1,73 @@ +// Copyright 2020 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" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type MyInt int +type ZeroStruct struct{} + +func TestZero(t *testing.T) { + var zeroValues = []interface{}{ + int8(0), + int16(0), + int(0), + int32(0), + int64(0), + uint8(0), + uint16(0), + uint(0), + uint32(0), + uint64(0), + MyInt(0), + reflect.ValueOf(0), + nil, + time.Time{}, + &time.Time{}, + nilTime, + ZeroStruct{}, + &ZeroStruct{}, + } + + for _, v := range zeroValues { + t.Run(fmt.Sprintf("%#v", v), func(t *testing.T) { + assert.True(t, IsZero(v)) + }) + } +} + +func TestIsValueZero(t *testing.T) { + var zeroReflectValues = []reflect.Value{ + reflect.ValueOf(int8(0)), + reflect.ValueOf(int16(0)), + reflect.ValueOf(int(0)), + reflect.ValueOf(int32(0)), + reflect.ValueOf(int64(0)), + reflect.ValueOf(uint8(0)), + reflect.ValueOf(uint16(0)), + reflect.ValueOf(uint(0)), + reflect.ValueOf(uint32(0)), + reflect.ValueOf(uint64(0)), + reflect.ValueOf(MyInt(0)), + reflect.ValueOf(time.Time{}), + reflect.ValueOf(&time.Time{}), + reflect.ValueOf(nilTime), + reflect.ValueOf(ZeroStruct{}), + reflect.ValueOf(&ZeroStruct{}), + } + + for _, v := range zeroReflectValues { + t.Run(fmt.Sprintf("%#v", v), func(t *testing.T) { + assert.True(t, IsValueZero(v)) + }) + } +} diff --git a/session_convert.go b/session_convert.go index 28866d4d..a6839947 100644 --- a/session_convert.go +++ b/session_convert.go @@ -6,7 +6,6 @@ package xorm import ( "database/sql" - "database/sql/driver" "errors" "fmt" "reflect" @@ -15,7 +14,6 @@ import ( "time" "xorm.io/xorm/convert" - "xorm.io/xorm/dialects" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" @@ -88,10 +86,6 @@ func (session *Session) byte2Time(col *schemas.Column, data []byte) (outTime tim return session.str2Time(col, string(data)) } -var ( - nullFloatType = reflect.TypeOf(sql.NullFloat64{}) -) - // 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 { @@ -533,133 +527,3 @@ func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Val return nil } - -// convert a field value of a struct to interface for put into db -func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect.Value) (interface{}, error) { - if fieldValue.CanAddr() { - if fieldConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - data, err := fieldConvert.ToDB() - if err != nil { - return 0, err - } - if col.SQLType.IsBlob() { - return data, nil - } - return string(data), nil - } - } - - if fieldConvert, ok := fieldValue.Interface().(convert.Conversion); ok { - data, err := fieldConvert.ToDB() - if err != nil { - return 0, err - } - if col.SQLType.IsBlob() { - return data, nil - } - return string(data), nil - } - - fieldType := fieldValue.Type() - k := fieldType.Kind() - if k == reflect.Ptr { - if fieldValue.IsNil() { - return nil, nil - } else if !fieldValue.IsValid() { - session.engine.logger.Warnf("the field [%s] is invalid", col.FieldName) - return nil, nil - } else { - // !nashtsai! deference pointer type to instance type - fieldValue = fieldValue.Elem() - fieldType = fieldValue.Type() - k = fieldType.Kind() - } - } - - switch k { - case reflect.Bool: - return fieldValue.Bool(), nil - case reflect.String: - return fieldValue.String(), nil - case reflect.Struct: - if fieldType.ConvertibleTo(schemas.TimeType) { - t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) - tf := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, t) - return tf, nil - } else if fieldType.ConvertibleTo(nullFloatType) { - t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64) - if !t.Valid { - return nil, nil - } - return t.Float64, nil - } - - if !col.SQLType.IsJson() { - // !! 增加支持driver.Valuer接口的结构,如sql.NullString - if v, ok := fieldValue.Interface().(driver.Valuer); ok { - return v.Value() - } - - fieldTable, err := session.engine.tagParser.ParseWithCache(fieldValue) - if err != nil { - return nil, err - } - if len(fieldTable.PrimaryKeys) == 1 { - pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName) - return pkField.Interface(), nil - } - return 0, fmt.Errorf("no primary key for col %v", col.Name) - } - - if col.SQLType.IsText() { - bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return 0, err - } - return string(bytes), nil - } else if col.SQLType.IsBlob() { - bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return 0, err - } - return bytes, nil - } - return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type()) - case reflect.Complex64, reflect.Complex128: - bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return 0, err - } - return string(bytes), nil - case reflect.Array, reflect.Slice, reflect.Map: - if !fieldValue.IsValid() { - return fieldValue.Interface(), nil - } - - if col.SQLType.IsText() { - bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return 0, err - } - return string(bytes), nil - } else if col.SQLType.IsBlob() { - var bytes []byte - var err error - if (k == reflect.Slice) && - (fieldValue.Type().Elem().Kind() == reflect.Uint8) { - bytes = fieldValue.Bytes() - } else { - bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface()) - if err != nil { - return 0, err - } - } - return bytes, nil - } - return nil, ErrUnSupportedType - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - return int64(fieldValue.Uint()), nil - default: - return fieldValue.Interface(), nil - } -} diff --git a/session_insert.go b/session_insert.go index b2e92309..12483aa3 100644 --- a/session_insert.go +++ b/session_insert.go @@ -176,7 +176,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error setColumnInt(bean, col, 1) }) } else { - arg, err := session.value2Interface(col, fieldValue) + arg, err := session.statement.Value2Interface(col, fieldValue) if err != nil { return 0, err } @@ -227,7 +227,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error setColumnInt(bean, col, 1) }) } else { - arg, err := session.value2Interface(col, fieldValue) + arg, err := session.statement.Value2Interface(col, fieldValue) if err != nil { return 0, err } @@ -567,25 +567,8 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac } fieldValue := *fieldValuePtr - if col.IsAutoIncrement { - switch fieldValue.Type().Kind() { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: - if fieldValue.Int() == 0 { - continue - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: - if fieldValue.Uint() == 0 { - continue - } - case reflect.String: - if len(fieldValue.String()) == 0 { - continue - } - case reflect.Ptr: - if fieldValue.Pointer() == 0 { - continue - } - } + if col.IsAutoIncrement && utils.IsValueZero(fieldValue) { + continue } // !evalphobia! set fieldValue as nil when column is nullable and zero-value @@ -609,7 +592,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac } else if col.IsVersion && session.statement.CheckVersion { args = append(args, 1) } else { - arg, err := session.value2Interface(col, fieldValue) + arg, err := session.statement.Value2Interface(col, fieldValue) if err != nil { return colNames, args, err } diff --git a/session_update.go b/session_update.go index f60f48e3..dadfaaca 100644 --- a/session_update.go +++ b/session_update.go @@ -473,25 +473,8 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac } fieldValue := *fieldValuePtr - if col.IsAutoIncrement { - switch fieldValue.Type().Kind() { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: - if fieldValue.Int() == 0 { - continue - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: - if fieldValue.Uint() == 0 { - continue - } - case reflect.String: - if len(fieldValue.String()) == 0 { - continue - } - case reflect.Ptr: - if fieldValue.Pointer() == 0 { - continue - } - } + if col.IsAutoIncrement && utils.IsValueZero(fieldValue) { + continue } if (col.IsDeleted && !session.statement.GetUnscoped()) || col.IsCreated { @@ -532,7 +515,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac } else if col.IsVersion && session.statement.CheckVersion { args = append(args, 1) } else { - arg, err := session.value2Interface(col, fieldValue) + arg, err := session.statement.Value2Interface(col, fieldValue) if err != nil { return colNames, args, err } From 67cf42799c978d825fb043deec6f796209aba9b8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Mar 2020 01:03:33 +0000 Subject: [PATCH 064/112] Remove duplicated code (#1588) Remove duplicated code Reviewed-on: https://gitea.com/xorm/xorm/pulls/1588 --- session_insert.go | 136 +++++++++++++++------------------------------- 1 file changed, 44 insertions(+), 92 deletions(-) diff --git a/session_insert.go b/session_insert.go index 12483aa3..923fe16c 100644 --- a/session_insert.go +++ b/session_insert.go @@ -137,106 +137,58 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error } // -- - if i == 0 { - for _, col := range table.Columns() { - ptrFieldValue, err := col.ValueOfV(&vv) + for _, col := range table.Columns() { + ptrFieldValue, err := col.ValueOfV(&vv) + if err != nil { + return 0, err + } + fieldValue := *ptrFieldValue + if col.IsAutoIncrement && utils.IsZero(fieldValue.Interface()) { + continue + } + if col.MapType == schemas.ONLYFROMDB { + continue + } + if col.IsDeleted { + continue + } + if session.statement.OmitColumnMap.Contain(col.Name) { + continue + } + if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { + continue + } + if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { + val, t := session.engine.nowTime(col) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) + } else if col.IsVersion && session.statement.CheckVersion { + args = append(args, 1) + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnInt(bean, col, 1) + }) + } else { + arg, err := session.statement.Value2Interface(col, fieldValue) if err != nil { return 0, err } - fieldValue := *ptrFieldValue - if col.IsAutoIncrement && utils.IsZero(fieldValue.Interface()) { - continue - } - if col.MapType == schemas.ONLYFROMDB { - continue - } - if col.IsDeleted { - continue - } - if session.statement.OmitColumnMap.Contain(col.Name) { - continue - } - if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { - continue - } - if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { - val, t := session.engine.nowTime(col) - args = append(args, val) - - var colName = col.Name - session.afterClosures = append(session.afterClosures, func(bean interface{}) { - col := table.GetColumn(colName) - setColumnTime(bean, col, t) - }) - } else if col.IsVersion && session.statement.CheckVersion { - args = append(args, 1) - var colName = col.Name - session.afterClosures = append(session.afterClosures, func(bean interface{}) { - col := table.GetColumn(colName) - setColumnInt(bean, col, 1) - }) - } else { - arg, err := session.statement.Value2Interface(col, fieldValue) - if err != nil { - return 0, err - } - args = append(args, arg) - } + args = append(args, arg) + } + if i == 0 { colNames = append(colNames, col.Name) cols = append(cols, col) - colPlaces = append(colPlaces, "?") - } - } else { - for _, col := range cols { - ptrFieldValue, err := col.ValueOfV(&vv) - if err != nil { - return 0, err - } - fieldValue := *ptrFieldValue - - if col.IsAutoIncrement && utils.IsZero(fieldValue.Interface()) { - continue - } - if col.MapType == schemas.ONLYFROMDB { - continue - } - if col.IsDeleted { - continue - } - if session.statement.OmitColumnMap.Contain(col.Name) { - continue - } - if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { - continue - } - if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { - val, t := session.engine.nowTime(col) - args = append(args, val) - - var colName = col.Name - session.afterClosures = append(session.afterClosures, func(bean interface{}) { - col := table.GetColumn(colName) - setColumnTime(bean, col, t) - }) - } else if col.IsVersion && session.statement.CheckVersion { - args = append(args, 1) - var colName = col.Name - session.afterClosures = append(session.afterClosures, func(bean interface{}) { - col := table.GetColumn(colName) - setColumnInt(bean, col, 1) - }) - } else { - arg, err := session.statement.Value2Interface(col, fieldValue) - if err != nil { - return 0, err - } - args = append(args, arg) - } - - colPlaces = append(colPlaces, "?") } + colPlaces = append(colPlaces, "?") } + colMultiPlaces = append(colMultiPlaces, strings.Join(colPlaces, ", ")) } cleanupProcessorsClosures(&session.beforeClosures) From f13883a725e6026a22d5188cd3d4bae0e324d764 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Mar 2020 03:01:32 +0000 Subject: [PATCH 065/112] check driver.Valuer response, and skip the column if nil (#1167) update tests Reviewed-on: https://gitea.com/xorm/xorm/pulls/1167 --- internal/statements/statement.go | 2 +- internal/statements/update.go | 3 ++ types_null_test.go | 61 ++++++++++++++++---------------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 4beb9a7e..d1fcaf59 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -838,7 +838,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, continue } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = valNul.Value() - if val == nil { + if val == nil && !requiredField { continue } } else { diff --git a/internal/statements/update.go b/internal/statements/update.go index 10f36778..2e502243 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -186,6 +186,9 @@ func (statement *Statement) BuildUpdates(bean interface{}, val = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) } else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = nulType.Value() + if val == nil && !requiredField { + continue + } } else { if !col.SQLType.IsJson() { table, err := statement.tagParser.ParseWithCache(fieldValue) diff --git a/types_null_test.go b/types_null_test.go index 665849ca..f62366b7 100644 --- a/types_null_test.go +++ b/types_null_test.go @@ -22,6 +22,7 @@ type NullType struct { Age sql.NullInt64 Height sql.NullFloat64 IsMan sql.NullBool `xorm:"null"` + Nil driver.Valuer CustomStruct CustomStruct `xorm:"varchar(64) null"` } @@ -72,42 +73,42 @@ func TestNullStructInsert(t *testing.T) { assert.NoError(t, prepareEngine()) assertSync(t, new(NullType)) - if true { - item := new(NullType) - _, err := testEngine.Insert(item) - assert.NoError(t, err) - assert.EqualValues(t, 1, item.Id) - } + item1 := new(NullType) + _, err := testEngine.Insert(item1) + assert.NoError(t, err) + assert.EqualValues(t, 1, item1.Id) - if true { + item := NullType{ + Name: sql.NullString{String: "haolei", Valid: true}, + Age: sql.NullInt64{Int64: 34, Valid: true}, + Height: sql.NullFloat64{Float64: 1.72, Valid: true}, + IsMan: sql.NullBool{Bool: true, Valid: true}, + Nil: nil, + } + _, err = testEngine.Insert(&item) + assert.NoError(t, err) + assert.EqualValues(t, 2, item.Id) + + items := []NullType{} + for i := 0; i < 5; i++ { item := NullType{ - Name: sql.NullString{String: "haolei", Valid: true}, - Age: sql.NullInt64{Int64: 34, Valid: true}, - Height: sql.NullFloat64{Float64: 1.72, Valid: true}, - IsMan: sql.NullBool{Bool: true, Valid: true}, + 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}, + IsMan: sql.NullBool{Bool: true, Valid: true}, + CustomStruct: CustomStruct{i, i + 1, i + 2}, + Nil: nil, } - _, err := testEngine.Insert(&item) - assert.NoError(t, err) - assert.EqualValues(t, 2, item.Id) + items = append(items, item) } - if true { - items := []NullType{} - for i := 0; i < 5; i++ { - item := NullType{ - 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}, - IsMan: sql.NullBool{Bool: true, Valid: true}, - CustomStruct: CustomStruct{i, i + 1, i + 2}, - } + _, err = testEngine.Insert(&items) + assert.NoError(t, err) - items = append(items, item) - } - - _, err := testEngine.Insert(&items) - assert.NoError(t, err) - } + items = make([]NullType, 0, 7) + err = testEngine.Find(&items) + assert.NoError(t, err) + assert.EqualValues(t, 7, len(items)) } func TestNullStructUpdate(t *testing.T) { From 36e26e35eda90fc396efa1fe87804290bd22c699 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Mar 2020 03:02:31 +0000 Subject: [PATCH 066/112] =?UTF-8?q?=E8=A7=A3=E5=86=B3Conversion=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3ToDB()=20([]byte,=20error)=E6=96=B9=E6=B3=95=E8=BF=94?= =?UTF-8?q?=E5=9B=9E*Type=E5=80=BC=E4=B8=BAnil=E6=97=B6,=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=B8=BA[]byte(nil)=E7=9A=84=E2=80=A6=20(#12?= =?UTF-8?q?96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add test 解决Conversion接口ToDB() ([]byte, error)方法返回*Type值为nil时,数据类型为[]byte(nil)的bug,解决*Type值为nil时插入数据变为""的bug Co-authored-by: peihexian Reviewed-on: https://gitea.com/xorm/xorm/pulls/1296 --- internal/statements/values.go | 3 +++ types_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/internal/statements/values.go b/internal/statements/values.go index b545a605..0ab174d6 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -44,6 +44,9 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl if col.SQLType.IsBlob() { return data, nil } + if nil == data { + return nil, nil + } return string(data), nil } diff --git a/types_test.go b/types_test.go index 77407e98..5cd01be2 100644 --- a/types_test.go +++ b/types_test.go @@ -119,10 +119,17 @@ type ConvConfig struct { } func (s *ConvConfig) FromDB(data []byte) error { + if data == nil { + s = nil + return nil + } return json.DefaultJSONHandler.Unmarshal(data, s) } func (s *ConvConfig) ToDB() ([]byte, error) { + if s == nil { + return nil, nil + } return json.DefaultJSONHandler.Marshal(s) } @@ -184,6 +191,30 @@ func TestConversion(t *testing.T) { assert.EqualValues(t, 2, len(c1.Slice)) assert.EqualValues(t, *c.Slice[0], *c1.Slice[0]) assert.EqualValues(t, *c.Slice[1], *c1.Slice[1]) + + cnt, err := testEngine.Where("1=1").Delete(new(ConvStruct)) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + c.Cfg2 = nil + + _, err = testEngine.Insert(c) + assert.NoError(t, err) + + c2 := new(ConvStruct) + has, err = testEngine.Get(c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, "prefix---tttt", string(c2.Conv)) + assert.NotNil(t, c2.Conv2) + assert.EqualValues(t, "prefix---"+s, *c2.Conv2) + assert.EqualValues(t, c.Cfg1, c2.Cfg1) + assert.Nil(t, c2.Cfg2) + assert.NotNil(t, c2.Cfg3) + assert.EqualValues(t, *c.Cfg3.(*ConvConfig), *c2.Cfg3.(*ConvConfig)) + 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]) } type MyInt int From 9d4594632fef598b76e1723641dfb01261c673d1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Mar 2020 04:46:46 +0000 Subject: [PATCH 067/112] Fix mssql issue with duplicate columns. (#1225) fix test fix test Fix mssql issue with duplicate columns. The `GetColumns()` method for the mssql dialect can return the same column multiple times if the column is in multiple indexes. Co-authored-by: Robert G. Jakabosky Reviewed-on: https://gitea.com/xorm/xorm/pulls/1225 --- dialects/mssql.go | 11 ++++++----- tags_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/dialects/mssql.go b/dialects/mssql.go index cad18a29..dd3f4247 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -335,14 +335,15 @@ func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, ma s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, "default_is_null" = (CASE WHEN c.text is null THEN 1 ELSE 0 END), replace(replace(isnull(c.text,''),'(',''),')','') as vdefault, - ISNULL(i.is_primary_key, 0), a.is_identity as is_identity + ISNULL(p.is_primary_key, 0), a.is_identity as is_identity from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id left join sys.syscomments c on a.default_object_id=c.id - LEFT OUTER JOIN - sys.index_columns ic ON ic.object_id = a.object_id AND ic.column_id = a.column_id - LEFT OUTER JOIN - sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id + LEFT OUTER JOIN (SELECT i.object_id, ic.column_id, i.is_primary_key + FROM sys.indexes i + LEFT JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id + WHERE i.is_primary_key = 1 + ) as p on p.object_id = a.object_id AND p.column_id = a.column_id where a.object_id=object_id('` + tableName + `')` rows, err := db.DB().QueryContext(ctx, s, args...) diff --git a/tags_test.go b/tags_test.go index ff578def..74822956 100644 --- a/tags_test.go +++ b/tags_test.go @@ -6,6 +6,7 @@ package xorm import ( "fmt" + "sort" "strings" "testing" "time" @@ -1295,3 +1296,34 @@ func TestVersion4(t *testing.T) { assert.EqualValues(t, v.Ver, 1) } } + +func TestIndexes(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TestIndexesStruct struct { + Id int64 + Name string `xorm:"index unique(s)"` + Email string `xorm:"index unique(s)"` + } + + assertSync(t, new(TestIndexesStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 3, len(tables[0].Columns())) + slice1 := []string{ + testEngine.GetColumnMapper().Obj2Table("Id"), + testEngine.GetColumnMapper().Obj2Table("Name"), + testEngine.GetColumnMapper().Obj2Table("Email"), + } + slice2 := []string{ + tables[0].Columns()[0].Name, + tables[0].Columns()[1].Name, + tables[0].Columns()[2].Name, + } + sort.Strings(slice1) + sort.Strings(slice2) + assert.EqualValues(t, slice1, slice2) + assert.EqualValues(t, 3, len(tables[0].Indexes)) +} From 2f95c750c348309ae09cfafa87166ea6f9a62cc3 Mon Sep 17 00:00:00 2001 From: Jerry Date: Tue, 10 Mar 2020 06:11:42 +0000 Subject: [PATCH 068/112] jerry:add postgre data_type array (#1589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix err,add postgres column ARRAY fix err,add postgres column ARRAY Co-authored-by: Jerry <85411418@qq.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1589 Reviewed-by: Lunny Xiao --- dialects/postgres.go | 2 ++ schemas/type.go | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/dialects/postgres.go b/dialects/postgres.go index 8412ad40..2111f195 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1099,6 +1099,8 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: 0, DefaultLength2: 0} case "oid": col.SQLType = schemas.SQLType{Name: schemas.BigInt, DefaultLength: 0, DefaultLength2: 0} + case "array": + col.SQLType = schemas.SQLType{Name: schemas.Array, DefaultLength: 0, DefaultLength2: 0} default: startIdx := strings.Index(strings.ToLower(dataType), "string(") if startIdx != -1 && strings.HasSuffix(dataType, ")") { diff --git a/schemas/type.go b/schemas/type.go index 39f1bf4e..89459a4d 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -34,6 +34,7 @@ const ( BLOB_TYPE TIME_TYPE NUMERIC_TYPE + ARRAY_TYPE ) func (s *SQLType) IsType(st int) bool { @@ -59,6 +60,10 @@ func (s *SQLType) IsNumeric() bool { return s.IsType(NUMERIC_TYPE) } +func (s *SQLType) IsArray() bool { + return s.IsType(ARRAY_TYPE) +} + func (s *SQLType) IsJson() bool { return s.Name == Json || s.Name == Jsonb } @@ -123,6 +128,8 @@ var ( Json = "JSON" Jsonb = "JSONB" + Array = "ARRAY" + SqlTypes = map[string]int{ Bit: NUMERIC_TYPE, TinyInt: NUMERIC_TYPE, @@ -180,6 +187,8 @@ var ( Serial: NUMERIC_TYPE, BigSerial: NUMERIC_TYPE, + + Array: ARRAY_TYPE, } intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"} From 6485adb722c65c7a7ee8adfebfe5fa4670c18d95 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Mar 2020 08:15:13 +0000 Subject: [PATCH 069/112] Fix table name (#1590) add test Fix table name Reviewed-on: https://gitea.com/xorm/xorm/pulls/1590 --- names/table_name.go | 30 ++++++++++++++++++++----- names/table_name_test.go | 47 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/names/table_name.go b/names/table_name.go index 6dd4e552..0afb1ae3 100644 --- a/names/table_name.go +++ b/names/table_name.go @@ -6,6 +6,7 @@ package names import ( "reflect" + "sync" ) // TableName table name interface to define customerize table name @@ -15,23 +16,40 @@ type TableName interface { var ( tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() + tvCache sync.Map ) func GetTableName(mapper Mapper, v reflect.Value) string { - if t, ok := v.Interface().(TableName); ok { - return t.TableName() - } if v.Type().Implements(tpTableName) { return v.Interface().(TableName).TableName() } + if v.Kind() == reflect.Ptr { v = v.Elem() - if t, ok := v.Interface().(TableName); ok { - return t.TableName() - } if v.Type().Implements(tpTableName) { return v.Interface().(TableName).TableName() } + } else if v.CanAddr() { + v1 := v.Addr() + if v1.Type().Implements(tpTableName) { + return v1.Interface().(TableName).TableName() + } + } else { + name, ok := tvCache.Load(v.Type()) + if ok { + if name.(string) != "" { + return name.(string) + } + } else { + v2 := reflect.New(v.Type()) + if v2.Type().Implements(tpTableName) { + tableName := v2.Interface().(TableName).TableName() + tvCache.Store(v.Type(), tableName) + return tableName + } + + tvCache.Store(v.Type(), "") + } } return mapper.Obj2Table(v.Type().Name()) diff --git a/names/table_name_test.go b/names/table_name_test.go index 1f20bfaa..76da4135 100644 --- a/names/table_name_test.go +++ b/names/table_name_test.go @@ -5,6 +5,7 @@ package names import ( + "fmt" "reflect" "testing" "time" @@ -43,8 +44,10 @@ func (MyGetCustomTableImpletation) TableName() string { type TestTableNameStruct struct{} +const getTestTableName = "my_test_table_name_struct" + func (t *TestTableNameStruct) TableName() string { - return "my_test_table_name_struct" + return getTestTableName } func TestGetTableName(t *testing.T) { @@ -85,13 +88,18 @@ func TestGetTableName(t *testing.T) { }, { SnakeMapper{}, - reflect.ValueOf(MyGetCustomTableImpletation{}), - getCustomTableName, + reflect.ValueOf(new(TestTableNameStruct)), + new(TestTableNameStruct).TableName(), }, { SnakeMapper{}, reflect.ValueOf(new(TestTableNameStruct)), - new(TestTableNameStruct).TableName(), + getTestTableName, + }, + { + SnakeMapper{}, + reflect.ValueOf(TestTableNameStruct{}), + getTestTableName, }, } @@ -99,3 +107,34 @@ func TestGetTableName(t *testing.T) { assert.EqualValues(t, kase.expectedTableName, GetTableName(kase.mapper, kase.v)) } } + +type OAuth2Application struct { +} + +// TableName sets the table name to `oauth2_application` +func (app *OAuth2Application) TableName() string { + return "oauth2_application" +} + +func TestGonicMapperCustomTable(t *testing.T) { + assert.EqualValues(t, "oauth2_application", + GetTableName(LintGonicMapper, reflect.ValueOf(new(OAuth2Application)))) + assert.EqualValues(t, "oauth2_application", + GetTableName(LintGonicMapper, reflect.ValueOf(OAuth2Application{}))) +} + +type MyTable struct { + Idx int +} + +func (t *MyTable) TableName() string { + return fmt.Sprintf("mytable_%d", t.Idx) +} + +func TestMyTable(t *testing.T) { + var table MyTable + for i := 0; i < 10; i++ { + table.Idx = i + assert.EqualValues(t, fmt.Sprintf("mytable_%d", i), GetTableName(SameMapper{}, reflect.ValueOf(&table))) + } +} From 537d82a6f7ca77ceb60ed5ca18a7fc872eb660c9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Mar 2020 13:15:18 +0000 Subject: [PATCH 070/112] Ignore schema when dbtype is not postgres (#1593) Ignore schema when dbtype is not postgres Reviewed-on: https://gitea.com/xorm/xorm/pulls/1593 --- dialects/dialect.go | 8 ++++++++ engine.go | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 35139817..4b9976f7 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -14,6 +14,7 @@ import ( "xorm.io/xorm/schemas" ) +// URI represents an uri to visit database type URI struct { DBType schemas.DBType Proto string @@ -29,6 +30,13 @@ type URI struct { Schema string } +// SetSchema set schema +func (uri URI) SetSchema(schema string) { + if uri.DBType == schemas.POSTGRES { + uri.Schema = schema + } +} + // Dialect represents a kind of database type Dialect interface { Init(*core.DB, *URI) error diff --git a/engine.go b/engine.go index 6dd4df3e..bc1d8b5f 100644 --- a/engine.go +++ b/engine.go @@ -1245,7 +1245,7 @@ func (engine *Engine) SetTZDatabase(tz *time.Location) { // SetSchema sets the schema of database func (engine *Engine) SetSchema(schema string) { - engine.dialect.URI().Schema = schema + engine.dialect.URI().SetSchema(schema) } // Unscoped always disable struct tag "deleted" From 94fd254638695349b5b57e6e271d3606f2d58696 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 11 Mar 2020 03:29:43 +0000 Subject: [PATCH 071/112] Support count with cols (#1595) Support count with cols Reviewed-on: https://gitea.com/xorm/xorm/pulls/1595 --- interface.go | 1 + internal/statements/query.go | 3 +++ session_stats_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/interface.go b/interface.go index 67b8d4b1..be4da707 100644 --- a/interface.go +++ b/interface.go @@ -59,6 +59,7 @@ type Interface interface { QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) Rows(bean interface{}) (*Rows, error) SetExpr(string, interface{}) *Session + Select(string) *Session SQL(interface{}, ...interface{}) *Session Sum(bean interface{}, colName string) (float64, error) SumInt(bean interface{}, colName string) (int64, error) diff --git a/internal/statements/query.go b/internal/statements/query.go index 1568259e..ab3021bf 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -153,6 +153,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, return sqlStr, append(statement.joinArgs, condArgs...), nil } +// GenCountSQL generates the SQL for counting func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil @@ -171,6 +172,8 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa if len(selectSQL) <= 0 { if statement.IsDistinct { selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr()) + } else if statement.ColumnStr() != "" { + selectSQL = fmt.Sprintf("count(%s)", statement.ColumnStr()) } else { selectSQL = "count(*)" } diff --git a/session_stats_test.go b/session_stats_test.go index d66a7e1f..1f11560b 100644 --- a/session_stats_test.go +++ b/session_stats_test.go @@ -274,3 +274,27 @@ func TestWithTableName(t *testing.T) { 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) +} From 367b15879e4c3dd07a4ea6e708665b0da2e14afa Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 11 Mar 2020 23:30:35 +0000 Subject: [PATCH 072/112] Fix bug when dump (#1597) Fix bug when dump Reviewed-on: https://gitea.com/xorm/xorm/pulls/1597 --- Makefile | 5 +++++ engine.go | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4444ebd0..88364f54 100644 --- a/Makefile +++ b/Makefile @@ -187,6 +187,11 @@ test-sqlite: go-check $(GO) test -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -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 -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -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 -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ diff --git a/engine.go b/engine.go index bc1d8b5f..f2e80855 100644 --- a/engine.go +++ b/engine.go @@ -363,7 +363,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch if dialect == nil { return errors.New("Unsupported database type") } - dialect.Init(nil, engine.dialect.URI()) + var destURI dialects.URI + uri := engine.dialect.URI() + destURI = *uri + dialect.Init(nil, &destURI) distDBName = string(tp[0]) } From c2bf301bdbd2e9a700dc02996b6ebd6daad0bbc3 Mon Sep 17 00:00:00 2001 From: joelBai Date: Thu, 12 Mar 2020 12:05:26 +0000 Subject: [PATCH 073/112] fix batch insert interface slice be panic (#1598) fix batch insert []interface{*struct, ...} be panic;add TestInsertMulti2Interface testcase Co-authored-by: baijinping Reviewed-on: https://gitea.com/xorm/xorm/pulls/1598 Reviewed-by: Lunny Xiao --- session_insert.go | 8 +++++++- session_insert_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/session_insert.go b/session_insert.go index 923fe16c..3a0a7066 100644 --- a/session_insert.go +++ b/session_insert.go @@ -122,7 +122,13 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error for i := 0; i < size; i++ { v := sliceValue.Index(i) - vv := reflect.Indirect(v) + var vv reflect.Value + switch v.Kind() { + case reflect.Interface: + vv = reflect.Indirect(v.Elem()) + default: + vv = reflect.Indirect(v) + } elemValue := v.Interface() var colPlaces []string diff --git a/session_insert_test.go b/session_insert_test.go index 09f647b8..3bc2d5b1 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -433,6 +433,37 @@ func TestInsertMulti2(t *testing.T) { assert.EqualValues(t, len(users2), cnt) } +func TestInsertMulti2Interface(t *testing.T) { + assert.NoError(t, prepareEngine()) + + assertSync(t, new(Userinfo)) + + users := []interface{}{ + Userinfo{Username: "xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + Userinfo{Username: "xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + Userinfo{Username: "xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + Userinfo{Username: "xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + } + + cnt, err := testEngine.Insert(&users) + if err != nil { + t.Error(err) + panic(err) + } + assert.EqualValues(t, len(users), cnt) + + users2 := []interface{}{ + &Userinfo{Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + &Userinfo{Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + &Userinfo{Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + &Userinfo{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + } + + cnt, err = testEngine.Insert(&users2) + assert.NoError(t, err) + assert.EqualValues(t, len(users2), cnt) +} + func TestInsertTwoTable(t *testing.T) { assert.NoError(t, prepareEngine()) From c56c8e122a29267d64c51e96d7442ae18ee698ff Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 13 Mar 2020 00:42:01 +0000 Subject: [PATCH 074/112] Fix master/slave bug (#1601) fix test Fix master/slave bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1601 --- engine.go | 1 - engine_group.go | 8 ++++---- engine_group_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 engine_group_test.go diff --git a/engine.go b/engine.go index f2e80855..c6fd5c7e 100644 --- a/engine.go +++ b/engine.go @@ -32,7 +32,6 @@ import ( // Commonly, an application only need one engine type Engine struct { cacherMgr *caches.Manager - db *core.DB defaultContext context.Context dialect dialects.Dialect engineGroup *EngineGroup diff --git a/engine_group.go b/engine_group.go index 38b64ca2..868d4dc9 100644 --- a/engine_group.go +++ b/engine_group.go @@ -161,17 +161,17 @@ func (eg *EngineGroup) SetMapper(mapper names.Mapper) { // SetMaxIdleConns set the max idle connections on pool, default is 2 func (eg *EngineGroup) SetMaxIdleConns(conns int) { - eg.Engine.db.SetMaxIdleConns(conns) + eg.Engine.dialect.DB().SetMaxIdleConns(conns) for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].db.SetMaxIdleConns(conns) + eg.slaves[i].dialect.DB().SetMaxIdleConns(conns) } } // SetMaxOpenConns is only available for go 1.2+ func (eg *EngineGroup) SetMaxOpenConns(conns int) { - eg.Engine.db.SetMaxOpenConns(conns) + eg.Engine.dialect.DB().SetMaxOpenConns(conns) for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].db.SetMaxOpenConns(conns) + eg.slaves[i].dialect.DB().SetMaxOpenConns(conns) } } diff --git a/engine_group_test.go b/engine_group_test.go new file mode 100644 index 00000000..76aad6d2 --- /dev/null +++ b/engine_group_test.go @@ -0,0 +1,33 @@ +// Copyright 2020 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 ( + "testing" + + "github.com/stretchr/testify/assert" + "xorm.io/xorm/log" + "xorm.io/xorm/schemas" +) + +func TestEngineGroup(t *testing.T) { + assert.NoError(t, prepareEngine()) + + master := testEngine.(*Engine) + if master.Dialect().URI().DBType == schemas.SQLITE { + t.Skip() + return + } + + eg, err := NewEngineGroup(master, []*Engine{master}) + assert.NoError(t, err) + + eg.SetMaxIdleConns(10) + eg.SetMaxOpenConns(100) + eg.SetTableMapper(master.GetTableMapper()) + eg.SetColumnMapper(master.GetColumnMapper()) + eg.SetLogLevel(log.LOG_INFO) + eg.ShowSQL(true) +} From 9500b233954f79ea89fe090990745019ccb6d8f1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 13 Mar 2020 08:57:34 +0000 Subject: [PATCH 075/112] Fix pk bug (#1602) Fix pk bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1602 --- internal/statements/pk.go | 79 ++++++++++++++++++++++++++++ internal/statements/statement.go | 49 +---------------- internal/statements/types.go | 16 ------ internal/statements/update.go | 90 ++++++++++++++++++-------------- schemas/column.go | 2 +- schemas/table.go | 8 +-- session_update.go | 2 +- session_update_test.go | 37 +++++++++++++ 8 files changed, 172 insertions(+), 111 deletions(-) create mode 100644 internal/statements/pk.go delete mode 100644 internal/statements/types.go diff --git a/internal/statements/pk.go b/internal/statements/pk.go new file mode 100644 index 00000000..b6ae0f23 --- /dev/null +++ b/internal/statements/pk.go @@ -0,0 +1,79 @@ +// 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 statements + +import ( + "fmt" + "reflect" + + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +var ( + ptrPkType = reflect.TypeOf(&schemas.PK{}) + pkType = reflect.TypeOf(schemas.PK{}) + stringType = reflect.TypeOf("") + intType = reflect.TypeOf(int64(0)) + uintType = reflect.TypeOf(uint64(0)) +) + +// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" +func (statement *Statement) ID(id interface{}) *Statement { + switch t := id.(type) { + case *schemas.PK: + statement.idParam = *t + case schemas.PK: + statement.idParam = t + case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + statement.idParam = schemas.PK{id} + default: + idValue := reflect.ValueOf(id) + idType := idValue.Type() + + switch idType.Kind() { + case reflect.String: + statement.idParam = schemas.PK{idValue.Convert(stringType).Interface()} + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + statement.idParam = schemas.PK{idValue.Convert(intType).Interface()} + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + statement.idParam = schemas.PK{idValue.Convert(uintType).Interface()} + case reflect.Slice: + if idType.ConvertibleTo(pkType) { + statement.idParam = idValue.Convert(pkType).Interface().(schemas.PK) + } + case reflect.Ptr: + if idType.ConvertibleTo(ptrPkType) { + statement.idParam = idValue.Convert(ptrPkType).Elem().Interface().(schemas.PK) + } + } + } + + if statement.idParam == nil { + statement.LastError = fmt.Errorf("ID param %#v is not supported", id) + } + + return statement +} + +func (statement *Statement) ProcessIDParam() error { + if statement.idParam == nil || statement.RefTable == nil { + return nil + } + + if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) { + fmt.Println("=====", statement.RefTable.PrimaryKeys, statement.idParam) + return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d", + len(statement.RefTable.PrimaryKeys), + len(statement.idParam), + ) + } + + for i, col := range statement.RefTable.PKColumns() { + var colName = statement.colName(col, statement.TableName()) + statement.cond = statement.cond.And(builder.Eq{colName: statement.idParam[i]}) + } + return nil +} diff --git a/internal/statements/statement.go b/internal/statements/statement.go index d1fcaf59..af94a9d9 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -41,7 +41,7 @@ type Statement struct { tagParser *tags.Parser Start int LimitN *int - idParam *schemas.PK + idParam schemas.PK OrderStr string JoinStr string joinArgs []interface{} @@ -319,34 +319,6 @@ func (statement *Statement) TableName() string { return statement.tableName } -// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" -func (statement *Statement) ID(id interface{}) *Statement { - idValue := reflect.ValueOf(id) - idType := reflect.TypeOf(idValue.Interface()) - - switch idType { - case ptrPkType: - if pkPtr, ok := (id).(*schemas.PK); ok { - statement.idParam = pkPtr - return statement - } - case pkType: - if pk, ok := (id).(schemas.PK); ok { - statement.idParam = &pk - return statement - } - } - - switch idType.Kind() { - case reflect.String: - statement.idParam = &schemas.PK{idValue.Convert(reflect.TypeOf("")).Interface()} - return statement - } - - statement.idParam = &schemas.PK{id} - return statement -} - // Incr Generate "Update ... Set column = column + arg" statement func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { if len(arg) > 0 { @@ -981,25 +953,6 @@ func convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { return "", nil, ErrUnSupportedType } -func (statement *Statement) ProcessIDParam() error { - if statement.idParam == nil || statement.RefTable == nil { - return nil - } - - if len(statement.RefTable.PrimaryKeys) != len(*statement.idParam) { - return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d", - len(statement.RefTable.PrimaryKeys), - len(*statement.idParam), - ) - } - - for i, col := range statement.RefTable.PKColumns() { - var colName = statement.colName(col, statement.TableName()) - statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.idParam))[i]}) - } - return nil -} - func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName bool) string { var colnames = make([]string, len(cols)) for i, col := range cols { diff --git a/internal/statements/types.go b/internal/statements/types.go deleted file mode 100644 index 0ff36f35..00000000 --- a/internal/statements/types.go +++ /dev/null @@ -1,16 +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 statements - -import ( - "reflect" - - "xorm.io/xorm/schemas" -) - -var ( - ptrPkType = reflect.TypeOf(&schemas.PK{}) - pkType = reflect.TypeOf(schemas.PK{}) -) diff --git a/internal/statements/update.go b/internal/statements/update.go index 2e502243..2bd7ddd3 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -18,58 +18,73 @@ import ( "xorm.io/xorm/schemas" ) +func (statement *Statement) ifAddColUpdate(col *schemas.Column, includeVersion, includeUpdated, includeNil, + includeAutoIncr, update bool) (bool, error) { + columnMap := statement.ColumnMap + omitColumnMap := statement.OmitColumnMap + unscoped := statement.unscoped + + if !includeVersion && col.IsVersion { + return false, nil + } + if col.IsCreated && !columnMap.Contain(col.Name) { + return false, nil + } + if !includeUpdated && col.IsUpdated { + return false, nil + } + if !includeAutoIncr && col.IsAutoIncrement { + return false, nil + } + if col.IsDeleted && !unscoped { + return false, nil + } + if omitColumnMap.Contain(col.Name) { + return false, nil + } + if len(columnMap) > 0 && !columnMap.Contain(col.Name) { + return false, nil + } + + if col.MapType == schemas.ONLYFROMDB { + return false, nil + } + + if statement.IncrColumns.IsColExist(col.Name) { + return false, nil + } else if statement.DecrColumns.IsColExist(col.Name) { + return false, nil + } else if statement.ExprColumns.IsColExist(col.Name) { + return false, nil + } + + return true, nil +} + // BuildUpdates auto generating update columnes and values according a struct -func (statement *Statement) BuildUpdates(bean interface{}, +func (statement *Statement) BuildUpdates(tableValue reflect.Value, includeVersion, includeUpdated, includeNil, includeAutoIncr, update bool) ([]string, []interface{}, error) { - //engine := statement.Engine table := statement.RefTable allUseBool := statement.allUseBool useAllCols := statement.useAllCols mustColumnMap := statement.MustColumnMap nullableMap := statement.NullableMap - columnMap := statement.ColumnMap - omitColumnMap := statement.OmitColumnMap - unscoped := statement.unscoped var colNames = make([]string, 0) var args = make([]interface{}, 0) + for _, col := range table.Columns() { - if !includeVersion && col.IsVersion { - continue + ok, err := statement.ifAddColUpdate(col, includeVersion, includeUpdated, includeNil, + includeAutoIncr, update) + if err != nil { + return nil, nil, err } - if col.IsCreated && !columnMap.Contain(col.Name) { - continue - } - if !includeUpdated && col.IsUpdated { - continue - } - if !includeAutoIncr && col.IsAutoIncrement { - continue - } - if col.IsDeleted && !unscoped { - continue - } - if omitColumnMap.Contain(col.Name) { - continue - } - if len(columnMap) > 0 && !columnMap.Contain(col.Name) { + if !ok { continue } - if col.MapType == schemas.ONLYFROMDB { - continue - } - - if statement.IncrColumns.IsColExist(col.Name) { - continue - } else if statement.DecrColumns.IsColExist(col.Name) { - continue - } else if statement.ExprColumns.IsColExist(col.Name) { - continue - } - - fieldValuePtr, err := col.ValueOf(bean) + fieldValuePtr, err := col.ValueOfV(&tableValue) if err != nil { return nil, nil, err } @@ -273,9 +288,6 @@ func (statement *Statement) BuildUpdates(bean interface{}, APPEND: args = append(args, val) - if col.IsPrimaryKey { - continue - } colNames = append(colNames, fmt.Sprintf("%v = ?", statement.quote(col.Name))) } diff --git a/schemas/column.go b/schemas/column.go index 9466f6a5..418629ac 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -21,7 +21,7 @@ const ( type Column struct { Name string TableName string - FieldName string + FieldName string // Avaiable only when parsed from a struct SQLType SQLType IsJSON bool Length int diff --git a/schemas/table.go b/schemas/table.go index 2dac3ea2..38596991 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -53,13 +53,9 @@ func (table *Table) ColumnsSeq() []string { } func (table *Table) columnsByName(name string) []*Column { - n := len(name) - for k := range table.columnsMap { - if len(k) != n { - continue - } + for k, cols := range table.columnsMap { if strings.EqualFold(k, name) { - return table.columnsMap[k] + return cols } } return nil diff --git a/session_update.go b/session_update.go index dadfaaca..62116c47 100644 --- a/session_update.go +++ b/session_update.go @@ -177,7 +177,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } if session.statement.ColumnStr() == "" { - colNames, args, err = session.statement.BuildUpdates(bean, false, false, + colNames, args, err = session.statement.BuildUpdates(v, false, false, false, false, true) } else { colNames, args, err = session.genUpdateColumns(bean) diff --git a/session_update_test.go b/session_update_test.go index d65e1207..5111222a 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -1303,3 +1303,40 @@ func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { assert.NoError(t, err) assertGetRecord() } + +func TestUpdateMultiplePK(t *testing.T) { + type TestUpdateMultiplePKStruct struct { + Id string `xorm:"notnull pk" description:"唯一ID号"` + Name string `xorm:"notnull pk" description:"名称"` + Value string `xorm:"notnull varchar(4000)" description:"值"` + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(TestUpdateMultiplePKStruct)) + + test := &TestUpdateMultiplePKStruct{ + Id: "ID1", + Name: "Name1", + Value: "1", + } + _, err := testEngine.Insert(test) + assert.NoError(t, err) + + test.Value = "2" + _, err = testEngine.Where("`id` = ? And `name` = ?", test.Id, test.Name).Cols("Value").Update(test) + assert.NoError(t, err) + + test.Value = "3" + num, err := testEngine.Where("`id` = ? And `name` = ?", test.Id, test.Name).Update(test) + assert.NoError(t, err) + assert.EqualValues(t, 1, num) + + test.Value = "4" + _, err = testEngine.ID([]interface{}{test.Id, test.Name}).Update(test) + assert.NoError(t, err) + + type MySlice []interface{} + test.Value = "5" + _, err = testEngine.ID(&MySlice{test.Id, test.Name}).Update(test) + assert.NoError(t, err) +} From b7b2b21a40ef497c3ff32faa9cf586c55c9f1513 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 15 Mar 2020 02:02:37 +0000 Subject: [PATCH 076/112] Fix dump/import bug (#1603) Fix bug Fix mssql Fix postgres Fix import test Fix dump/import bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1603 --- dialects/postgres.go | 2 ++ engine.go | 51 ++++++++------------------------------ engine_test.go | 33 +++++++++++++++++++++++++ interface.go | 1 + session_schema.go | 56 ++++++++++++++++++++++++++++++++++++++++++ session_schema_test.go | 9 ------- 6 files changed, 102 insertions(+), 50 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 2111f195..6fd9e64a 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1071,6 +1071,8 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att col.DefaultIsEmpty = false if strings.HasPrefix(col.Default, "nextval(") { col.IsAutoIncrement = true + col.Default = "" + col.DefaultIsEmpty = true } } else { col.DefaultIsEmpty = true diff --git a/engine.go b/engine.go index c6fd5c7e..99412c4f 100644 --- a/engine.go +++ b/engine.go @@ -5,8 +5,6 @@ package xorm import ( - "bufio" - "bytes" "context" "database/sql" "errors" @@ -389,6 +387,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } + if len(table.PKColumns()) > 0 && engine.dialect.URI().DBType == schemas.MSSQL { + fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", table.Name) + } + for _, index := range table.Indexes { _, err = io.WriteString(w, dialect.CreateIndexSQL(table.Name, index)+";\n") if err != nil { @@ -1160,49 +1162,16 @@ func (engine *Engine) SumsInt(bean interface{}, colNames ...string) ([]int64, er // ImportFile SQL DDL file func (engine *Engine) ImportFile(ddlPath string) ([]sql.Result, error) { - file, err := os.Open(ddlPath) - if err != nil { - return nil, err - } - defer file.Close() - return engine.Import(file) + session := engine.NewSession() + defer session.Close() + return session.ImportFile(ddlPath) } // Import SQL DDL from io.Reader func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { - var results []sql.Result - var lastError error - 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 - } - if i := bytes.IndexByte(data, ';'); i >= 0 { - 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 - } - // Request more data. - return 0, nil, nil - } - - scanner.Split(semiColSpliter) - - for scanner.Scan() { - query := strings.Trim(scanner.Text(), " \t\n\r") - if len(query) > 0 { - result, err := engine.DB().ExecContext(engine.defaultContext, query) - results = append(results, result) - if err != nil { - return nil, err - } - } - } - - return results, lastError + session := engine.NewSession() + defer session.Close() + return session.Import(r) } // nowTime return current time diff --git a/engine_test.go b/engine_test.go index b82ee96a..459d63c4 100644 --- a/engine_test.go +++ b/engine_test.go @@ -7,6 +7,7 @@ package xorm import ( "context" "fmt" + "os" "testing" "time" @@ -64,3 +65,35 @@ func TestAutoTransaction(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, false, has) } + +func TestDump(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TestDumpStruct struct { + Id int64 + Name string + } + + assertSync(t, new(TestDumpStruct)) + + testEngine.Insert([]TestDumpStruct{ + {Name: "1"}, + {Name: "2\n"}, + {Name: "3;"}, + {Name: "4\n;\n''"}, + {Name: "5'\n"}, + }) + + fp := testEngine.Dialect().URI().DBName + ".sql" + os.Remove(fp) + assert.NoError(t, testEngine.DumpAllToFile(fp)) + + assert.NoError(t, prepareEngine()) + + sess := testEngine.NewSession() + defer sess.Close() + assert.NoError(t, sess.Begin()) + _, err := sess.ImportFile(fp) + assert.NoError(t, err) + assert.NoError(t, sess.Commit()) +} diff --git a/interface.go b/interface.go index be4da707..262a2cfe 100644 --- a/interface.go +++ b/interface.go @@ -92,6 +92,7 @@ type EngineInterface interface { GetTableMapper() names.Mapper GetTZDatabase() *time.Location GetTZLocation() *time.Location + ImportFile(fp string) ([]sql.Result, error) MapCacher(interface{}, caches.Cacher) error NewSession() *Session NoAutoTime() *Session diff --git a/session_schema.go b/session_schema.go index ca4e2d75..84eb586e 100644 --- a/session_schema.go +++ b/session_schema.go @@ -5,8 +5,11 @@ package xorm import ( + "bufio" "database/sql" "fmt" + "io" + "os" "strings" "xorm.io/xorm/internal/utils" @@ -432,3 +435,56 @@ func (session *Session) Sync2(beans ...interface{}) error { return nil } + +// ImportFile SQL DDL file +func (session *Session) ImportFile(ddlPath string) ([]sql.Result, error) { + file, err := os.Open(ddlPath) + if err != nil { + return nil, err + } + defer file.Close() + return session.Import(file) +} + +// 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 inSingleQuote bool + semiColSpliter := func(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + for i, b := range data { + 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 + } + // Request more data. + return 0, nil, nil + } + + scanner.Split(semiColSpliter) + + for scanner.Scan() { + 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 + } + } + } + + return results, lastError +} diff --git a/session_schema_test.go b/session_schema_test.go index a20a1f97..37a1246b 100644 --- a/session_schema_test.go +++ b/session_schema_test.go @@ -6,7 +6,6 @@ package xorm import ( "fmt" - "os" "testing" "time" @@ -210,14 +209,6 @@ func TestCustomTableName(t *testing.T) { assert.NoError(t, testEngine.CreateTables(c)) } -func TestDump(t *testing.T) { - assert.NoError(t, prepareEngine()) - - fp := testEngine.Dialect().URI().DBName + ".sql" - os.Remove(fp) - assert.NoError(t, testEngine.DumpAllToFile(fp)) -} - type IndexOrUnique struct { Id int64 Index int `xorm:"index"` From a7a1dc5c427ebfe33ecb6b0bd0903e4f9d5650a8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 17 Mar 2020 06:50:06 +0000 Subject: [PATCH 077/112] Fix setschema (#1606) Fix schema Fix setschema Reviewed-on: https://gitea.com/xorm/xorm/pulls/1606 --- dialects/dialect.go | 2 +- dialects/postgres.go | 11 ++--------- engine.go | 41 +++++++++++++++++++++-------------------- engine_test.go | 13 +++++++++++++ 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 4b9976f7..4fdf35e9 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -31,7 +31,7 @@ type URI struct { } // SetSchema set schema -func (uri URI) SetSchema(schema string) { +func (uri *URI) SetSchema(schema string) { if uri.DBType == schemas.POSTGRES { uri.Schema = schema } diff --git a/dialects/postgres.go b/dialects/postgres.go index 6fd9e64a..0a851fe2 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1000,7 +1000,7 @@ func (db *postgres) IsColumnExist(ctx context.Context, tableName, colName string } func (db *postgres) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { - args := []interface{}{tableName} + args := []interface{}{db.uri.Schema, tableName, db.uri.Schema} s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey @@ -1011,14 +1011,7 @@ FROM pg_attribute f 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 LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name -WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;` - - var f string - if len(db.uri.Schema) != 0 { - args = append(args, db.uri.Schema) - f = " AND s.table_schema = $2" - } - s = fmt.Sprintf(s, f) +WHERE n.nspname= $1 AND c.relkind = 'r'::char AND c.relname = $2 AND s.table_schema = $3 AND f.attnum > 0 ORDER BY f.attnum;` rows, err := db.DB().QueryContext(ctx, s, args...) if err != nil { diff --git a/engine.go b/engine.go index 99412c4f..52865a3b 100644 --- a/engine.go +++ b/engine.go @@ -350,49 +350,50 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch // 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 { - var dialect dialects.Dialect - var distDBName string + var dstDialect dialects.Dialect if len(tp) == 0 { - dialect = engine.dialect - distDBName = string(engine.dialect.URI().DBType) + dstDialect = engine.dialect } else { - dialect = dialects.QueryDialect(tp[0]) - if dialect == nil { + dstDialect = dialects.QueryDialect(tp[0]) + if dstDialect == nil { return errors.New("Unsupported database type") } - var destURI dialects.URI + uri := engine.dialect.URI() - destURI = *uri - dialect.Init(nil, &destURI) - distDBName = string(tp[0]) + destURI := *uri + dstDialect.Init(nil, &destURI) } _, 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, strings.ToUpper(distDBName))) + time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.URI().DBType, dstDialect.URI().DBType)) if err != nil { return err } for i, table := range tables { + tableName := table.Name + if dstDialect.URI().Schema != "" { + tableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, table.Name) + } if i > 0 { _, err = io.WriteString(w, "\n") if err != nil { return err } } - sqls, _ := dialect.CreateTableSQL(table, "") + sqls, _ := dstDialect.CreateTableSQL(table, tableName) for _, s := range sqls { _, err = io.WriteString(w, s+";\n") if err != nil { return err } } - if len(table.PKColumns()) > 0 && engine.dialect.URI().DBType == schemas.MSSQL { + if len(table.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL { fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", table.Name) } for _, index := range table.Indexes { - _, err = io.WriteString(w, dialect.CreateIndexSQL(table.Name, index)+";\n") + _, err = io.WriteString(w, dstDialect.CreateIndexSQL(table.Name, index)+";\n") if err != nil { return err } @@ -400,9 +401,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch cols := table.ColumnsSeq() colNames := engine.dialect.Quoter().Join(cols, ", ") - destColNames := dialect.Quoter().Join(cols, ", ") + destColNames := dstDialect.Quoter().Join(cols, ", ") - rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(table.Name)) + rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(tableName)) if err != nil { return err } @@ -415,7 +416,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } - _, err = io.WriteString(w, "INSERT INTO "+dialect.Quoter().Quote(table.Name)+" ("+destColNames+") VALUES (") + _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(tableName)+" ("+destColNames+") VALUES (") if err != nil { return err } @@ -438,7 +439,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } } else if col.SQLType.IsBlob() { if reflect.TypeOf(d).Kind() == reflect.Slice { - temp += fmt.Sprintf(", %s", dialect.FormatBytes(d.([]byte))) + temp += fmt.Sprintf(", %s", dstDialect.FormatBytes(d.([]byte))) } else if reflect.TypeOf(d).Kind() == reflect.String { temp += fmt.Sprintf(", '%s'", d.(string)) } @@ -485,8 +486,8 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } // FIXME: Hack for postgres - if dialect.URI().DBType == schemas.POSTGRES && table.AutoIncrColumn() != nil { - _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quoter().Quote(table.Name)+"), 1), false);\n") + 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") if err != nil { return err } diff --git a/engine_test.go b/engine_test.go index 459d63c4..ab454d0d 100644 --- a/engine_test.go +++ b/engine_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/schemas" ) func TestPingContext(t *testing.T) { @@ -97,3 +98,15 @@ func TestDump(t *testing.T) { assert.NoError(t, err) assert.NoError(t, sess.Commit()) } + +func TestSetSchema(t *testing.T) { + assert.NoError(t, prepareEngine()) + + if testEngine.Dialect().URI().DBType == schemas.POSTGRES { + oldSchema := testEngine.Dialect().URI().Schema + testEngine.SetSchema("my_schema") + assert.EqualValues(t, "my_schema", testEngine.Dialect().URI().Schema) + testEngine.SetSchema(oldSchema) + assert.EqualValues(t, oldSchema, testEngine.Dialect().URI().Schema) + } +} From af30c17b0de4b9c1cf192ec9ccf7f4ccdc857794 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 21 Mar 2020 16:05:12 +0000 Subject: [PATCH 078/112] Fix dump bug (#1613) Fix dump Fix dump bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1613 --- engine.go | 6 +++++- engine_test.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/engine.go b/engine.go index 52865a3b..4694e1c0 100644 --- a/engine.go +++ b/engine.go @@ -375,6 +375,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch if dstDialect.URI().Schema != "" { tableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, table.Name) } + originalTableName := table.Name + if engine.dialect.URI().Schema != "" { + originalTableName = fmt.Sprintf("%s.%s", engine.dialect.URI().Schema, table.Name) + } if i > 0 { _, err = io.WriteString(w, "\n") if err != nil { @@ -403,7 +407,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch colNames := engine.dialect.Quoter().Join(cols, ", ") destColNames := dstDialect.Quoter().Join(cols, ", ") - rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(tableName)) + rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(originalTableName)) if err != nil { return err } diff --git a/engine_test.go b/engine_test.go index ab454d0d..d6da5f33 100644 --- a/engine_test.go +++ b/engine_test.go @@ -97,6 +97,12 @@ func TestDump(t *testing.T) { _, err := sess.ImportFile(fp) assert.NoError(t, err) assert.NoError(t, sess.Commit()) + + for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} { + t.Run(fmt.Sprintf("dump_%v", tp), func(t *testing.T) { + assert.NoError(t, testEngine.DumpAllToFile(fp, tp)) + }) + } } func TestSetSchema(t *testing.T) { From 5575e839dfd3daf93b9e2ccf4a62cd6a16084c54 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 22 Mar 2020 06:28:25 +0000 Subject: [PATCH 079/112] Add changelog for v1.0.0 (#1614) Add changelog for v1.0.0 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1614 --- .changelog.yml | 4 ---- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 23 +++++++++------------ README_CN.md | 22 +++++++++----------- go.mod | 2 +- go.sum | 3 +++ 6 files changed, 78 insertions(+), 32 deletions(-) diff --git a/.changelog.yml b/.changelog.yml index 4ab1de93..1303c9cc 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -36,10 +36,6 @@ groups: name: TESTING labels: - kind/testing - - - name: TRANSLATION - labels: - - kind/translation - name: BUILD labels: diff --git a/CHANGELOG.md b/CHANGELOG.md index b5917cf6..85c59d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,53 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. -## [1.0.0](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1242) - to be released +## [1.0.0](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1242) - 2020-03-22 +* BREAKING + * Add context for dialects (#1558) + * Move zero functions to a standalone package (#1548) + * Merge core package back into the main repository and split into serval sub packages. (#1543) +* FEATURES + * Use a new ContextLogger interface to implement logger (#1557) * BUGFIXES + * Fix setschema (#1606) + * Fix dump/import bug (#1603) + * Fix pk bug (#1602) + * Fix master/slave bug (#1601) + * Fix bug when dump (#1597) + * Ignore schema when dbtype is not postgres (#1593) + * Fix table name (#1590) + * Fix find alias bug (#1581) + * Fix rows bug (#1576) + * Fix map with cols (#1575) + * Fix bug on deleted with join (#1570) + * Improve quote policy (#1567) + * Fix break session sql enable feature (#1566) * Fix mssql quote (#1535) * Fix join table name quote bug (#1534) + * Fix mssql issue with duplicate columns. (#1225) + * Fix mysql8.0 sync failed (#808) * ENHANCEMENTS + * Fix batch insert interface slice be panic (#1598) + * Move some codes to statement sub package (#1574) + * Remove circle file (#1569) + * Move statement as a sub package (#1564) + * Move maptype to tag parser (#1561) + * Move caches to manager (#1553) + * Improve code (#1552) + * Improve some codes (#1551) + * Improve statement (#1549) + * Move tag parser related codes as a standalone sub package (#1547) * Move reserve words related files into dialects sub package (#1544) - * Merge core package back into the main repository and split into serval sub packages. (#1543) + * Fix `Conversion` method `ToDB() ([]byte, error)` return type is nil (#1296) + * Check driver.Valuer response, and skip the column if nil (#1167) + * Add cockroach support and tests (#896) +* TESTING + * Improve tests (#1572) +* BUILD + * Add changelog file and tool configuration (#1546) +* DOCS + * Fix outdate changelog (#1565) ## old changelog @@ -106,16 +145,29 @@ without substantial changes to our git log. * **v0.2.3** : Improved documents; Optimistic Locking support; Timestamp with time zone support; Mapper change to tableMapper and columnMapper & added PrefixMapper & SuffixMapper support custom table or column name's prefix and suffix;Insert now return affected, err instead of id, err; Added UseBool & Distinct; + * **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handler;Added SetMaxConns(go1.2+) support; some bugs fixed. + * **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md); some bug fixed. + * **v0.2.0** : Added Cache supported, select is speeder up 3~5x; Added SameMapper for same name between struct and table; Added Sync method for auto added tables, columns, indexes; + * **v0.1.9** : Added postgres and mymysql supported; Added ` and ? supported on Raw SQL even if postgres; Added Cols, StoreEngine, Charset function, Added many column data type supported, please see [Mapping Rules](#mapping). + * **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping). + * **v0.1.7** : Added IConnectPool interface and NoneConnectPool, SysConnectPool, SimpleConnectPool the three implements. You can choose one of them and the default is SysConnectPool. You can customrize your own connection pool. struct Engine added Close method, It should be invoked before system exit. + * **v0.1.6** : Added conversion interface support; added struct derive support; added single mapping support + * **v0.1.5** : Added multi threads support; added Sql() function for struct query; Get function changed return inteface; MakeSession and Create are instead with NewSession and NewEngine. + * **v0.1.4** : Added simple cascade load support; added more data type supports. + * **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support + * **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction + * **v0.1.1** : Add Id, In functions and improved README + * **v0.1.0** : Initial release. \ No newline at end of file diff --git a/README.md b/README.md index 05a15cef..2dc4d6eb 100644 --- a/README.md +++ b/README.md @@ -8,33 +8,30 @@ Xorm is a simple and powerful ORM for Go. [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +## Notice + +v1.0.0 has some break changes from v0.8.2. + +- Removed some non gonic function name `Id`, `Sql`, please use `ID`, `SQL` instead. +- Removed the dependent from `xorm.io/core` and moved the codes to `xorm.io/xorm/core`, `xorm.io/xorm/names`, `xorm.io/xorm/schemas` and others. +- Renamed some interface names. i.e. `core.IMapper` -> `names.Mapper`, `core.ILogger` -> `log.Logger`. + ## Features * Struct <-> Table Mapping Support - * Chainable APIs - * Transaction Support - * Both ORM and raw SQL operation Support - * Sync database schema Support - * Query Cache speed up - * Database Reverse support via [xorm.io/reverse](https://xorm.io/reverse) - * Simple cascade loading support - * Optimistic Locking support - * SQL Builder support via [xorm.io/builder](https://xorm.io/builder) - * Automatical Read/Write seperatelly - * Postgres schema support - * Context Cache support +* Support log/SQLLog context ## Drivers Support @@ -64,7 +61,7 @@ Drivers for Go's sql package which currently support database/sql includes: * [Manual](http://xorm.io/docs) -* [GoDoc](http://godoc.org/xorm.io/xorm) +* [GoDoc](http://pkg.go.dev/xorm.io/xorm) ## Quick Start diff --git a/README_CN.md b/README_CN.md index d9ee6d8d..f6f88310 100644 --- a/README_CN.md +++ b/README_CN.md @@ -8,31 +8,29 @@ xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操 [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +## Notice + +v1.0.0 相对于 v0.8.2 有以下不兼容的变更: + +- 移除了部分不符合Go语言命名的函数,如 `Id`, `Sql`,请使用 `ID`, `SQL` 替代。 +- 删除了对 `xorm.io/core` 的依赖。大部分代码迁移到了 `xorm.io/xorm/core`, `xorm.io/xorm/names`, `xorm.io/xorm/schemas` 等等几个包中. +- 重命名了几个结构体,如: `core.IMapper` -> `names.Mapper`, `core.ILogger` -> `log.Logger`. + ## 特性 * 支持 Struct 和数据库表之间的灵活映射,并支持自动同步 - * 事务支持 - * 同时支持原始SQL语句和ORM操作的混合执行 - * 使用连写来简化调用 - * 支持使用ID, In, Where, Limit, Join, Having, Table, SQL, Cols等函数和结构体等方式作为条件 - * 支持级联加载Struct - * Schema支持(仅Postgres) - * 支持缓存 - * 通过 [xorm.io/reverse](https://xorm.io/reverse) 支持根据数据库自动生成 xorm 结构体 - * 支持记录版本(即乐观锁) - * 通过 [xorm.io/builder](https://xorm.io/builder) 内置 SQL Builder 支持 - * 上下文缓存支持 +* 支持日志上下文 ## 驱动支持 @@ -62,7 +60,7 @@ xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操 * [操作指南](http://xorm.io/docs) -* [Godoc代码文档](http://godoc.org/xorm.io/xorm) +* [Godoc代码文档](http://pkg.go.dev/xorm.io/xorm) # 快速开始 diff --git a/go.mod b/go.mod index e81ff392..ff42c0f7 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,5 @@ require ( github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 google.golang.org/appengine v1.6.0 // indirect - xorm.io/builder v0.3.6 + xorm.io/builder v0.3.7 ) diff --git a/go.sum b/go.sum index 42a8bdd3..33e5c456 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ 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= cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -159,3 +160,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= +xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= +xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= From 2ece3bacbb9e7f6677c08874a429add639ae7060 Mon Sep 17 00:00:00 2001 From: tomoyamachi Date: Mon, 23 Mar 2020 02:03:04 +0000 Subject: [PATCH 080/112] Oracle : Local Naming Method (#1515) go mod tidy stop using xorm.io/core Merge branch 'master' into fix-oracle-db revert a goracle parser function add TestParseOracleConnStr Oracle : Local Naming Method Co-authored-by: Tomoya AMACHI Reviewed-on: https://gitea.com/xorm/xorm/pulls/1515 Reviewed-by: Lunny Xiao --- dialects/oracle.go | 2 +- dialects/oracle_test.go | 34 ++++++++++++++++++++++++++++++++++ go.sum | 5 +---- 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 dialects/oracle_test.go diff --git a/dialects/oracle.go b/dialects/oracle.go index c48d32b9..466b6a45 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -842,7 +842,7 @@ func (p *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { db.DBName = match } } - if db.DBName == "" { + if db.DBName == "" && len(matches) != 0 { return nil, errors.New("dbname is empty") } return db, nil diff --git a/dialects/oracle_test.go b/dialects/oracle_test.go new file mode 100644 index 00000000..9c3a93f5 --- /dev/null +++ b/dialects/oracle_test.go @@ -0,0 +1,34 @@ +package dialects + +import ( + "reflect" + "testing" +) + +func TestParseOracleConnStr(t *testing.T) { + tests := []struct { + in string + expected string + valid bool + }{ + {"user/pass@tcp(server:1521)/db", "db", true}, + {"user/pass@server:1521/db", "db", true}, + // test for net service name : https://docs.oracle.com/cd/B13789_01/network.101/b10775/glossary.htm#i998113 + {"user/pass@server:1521", "", true}, + {"user/pass@", "", false}, + {"user/pass", "", false}, + {"", "", false}, + } + driver := QueryDriver("oci8") + for _, test := range tests { + t.Run(test.in, func(t *testing.T) { + driver := driver + uri, err := driver.Parse("oci8", 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) + } + }) + } +} diff --git a/go.sum b/go.sum index 33e5c456..84f9126e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ 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= cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -26,8 +27,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= -github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -158,7 +157,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= -xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= From 3bddd228e71f9a80bc9377141facc6c3497ebb68 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 23 Mar 2020 14:38:01 +0000 Subject: [PATCH 081/112] Fix find and count bug (#1618) Fix find and count bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1618 --- session_find.go | 9 +++++++++ session_find_test.go | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/session_find.go b/session_find.go index 960c1085..45811cd7 100644 --- a/session_find.go +++ b/session_find.go @@ -11,6 +11,7 @@ import ( "xorm.io/builder" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/statements" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -387,6 +388,12 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in slices := reflect.New(reflect.SliceOf(t)) beans := slices.Interface() + statement := session.statement + session.statement = statements.NewStatement( + session.engine.dialect, + session.engine.tagParser, + session.engine.DatabaseTZ, + ) if len(table.PrimaryKeys) == 1 { ff := make([]interface{}, 0, len(ides)) for _, ie := range ides { @@ -409,6 +416,8 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in return err } + session.statement = statement + vs := reflect.Indirect(reflect.ValueOf(beans)) for i := 0; i < vs.Len(); i++ { rv := vs.Index(i) diff --git a/session_find_test.go b/session_find_test.go index c16d0a1b..70ed988c 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -493,6 +493,12 @@ func TestFindAndCountOneFunc(t *testing.T) { assert.EqualValues(t, 2, cnt) var results = make([]FindAndCountStruct, 0, 2) + cnt, err = testEngine.Limit(1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + + results = make([]FindAndCountStruct, 0, 2) cnt, err = testEngine.FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 2, len(results)) From 656cf6e5d6a504b3cf883dd052ee972d01e1307a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 23 Mar 2020 14:39:52 +0000 Subject: [PATCH 082/112] Fix find and count bug (#1622) Fix find and count bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1622 --- internal/statements/statement.go | 2 +- session_find_test.go | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index af94a9d9..22625650 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -903,7 +903,7 @@ func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, i } func (statement *Statement) mergeConds(bean interface{}) error { - if !statement.NoAutoCondition { + if !statement.NoAutoCondition && statement.RefTable != nil { var addedTableName = (len(statement.JoinStr) > 0) autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName) if err != nil { diff --git a/session_find_test.go b/session_find_test.go index 70ed988c..4857859c 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -525,10 +525,17 @@ func TestFindAndCountOneFunc(t *testing.T) { results = make([]FindAndCountStruct, 0, 1) cnt, err = testEngine.Where("msg = ?", true).Desc("id"). - Limit(1).FindAndCount(&results) + Limit(1).Cols("content").FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, cnt) + + ids := make([]int64, 0, 2) + tableName := testEngine.GetTableMapper().Obj2Table("FindAndCountStruct") + cnt, err = testEngine.Table(tableName).Limit(1).Cols("id").FindAndCount(&ids) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(ids)) + assert.EqualValues(t, 2, cnt) } type FindMapDevice struct { From 79cdec7d88155393f7f830de7e50094ba4220a01 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Mar 2020 00:48:54 +0000 Subject: [PATCH 083/112] Fix duplicated deleted condition on FindAndCount (#1619) Fix duplicated deleted condition on FindAndCount Reviewed-on: https://gitea.com/xorm/xorm/pulls/1619 --- session_find.go | 3 ++- session_find_test.go | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/session_find.go b/session_find.go index 45811cd7..6fbca695 100644 --- a/session_find.go +++ b/session_find.go @@ -61,7 +61,8 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte session.statement.OrderStr = "" } - return session.Count(reflect.New(sliceElementType).Interface()) + // session has stored the conditions so we use `unscoped` to avoid duplicated condition. + return session.Unscoped().Count(reflect.New(sliceElementType).Interface()) } func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error { diff --git a/session_find_test.go b/session_find_test.go index 4857859c..3c8860c5 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -538,6 +538,21 @@ func TestFindAndCountOneFunc(t *testing.T) { assert.EqualValues(t, 2, cnt) } +func TestFindAndCountOneFuncWithDeleted(t *testing.T) { + type CommentWithDeleted struct { + Id int `xorm:"pk autoincr"` + DeletedAt int64 `xorm:"deleted notnull default(0) index"` + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(CommentWithDeleted)) + + var comments []CommentWithDeleted + cnt, err := testEngine.FindAndCount(&comments) + assert.NoError(t, err) + assert.EqualValues(t, 0, cnt) +} + type FindMapDevice struct { Deviceid string `xorm:"pk"` Status int From 5053c3570182718173969ce3538554e25c2d6666 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Mar 2020 02:19:24 +0000 Subject: [PATCH 084/112] Don't keep db on dialects (#1623) don't keep db on dialects Reviewed-on: https://gitea.com/xorm/xorm/pulls/1623 --- core/db.go | 4 ++++ core/interface.go | 22 ++++++++++++++++++++++ core/stmt.go | 2 +- core/tx.go | 7 ++++++- dialects/dialect.go | 30 ++++++++++++------------------ dialects/driver.go | 8 +------- dialects/mssql.go | 24 ++++++++++++------------ dialects/mysql.go | 20 ++++++++++---------- dialects/oracle.go | 24 ++++++++++++------------ dialects/postgres.go | 26 +++++++++++++------------- dialects/sqlite3.go | 24 ++++++++++++------------ engine.go | 13 +++++++------ engine_group.go | 8 ++++---- session.go | 9 ++++++++- session_schema.go | 8 ++++---- xorm.go | 7 +++++++ 16 files changed, 135 insertions(+), 101 deletions(-) create mode 100644 core/interface.go diff --git a/core/db.go b/core/db.go index 9aa771ba..6845565d 100644 --- a/core/db.go +++ b/core/db.go @@ -77,6 +77,10 @@ type cacheStruct struct { idx int } +var ( + _ QueryExecuter = &DB{} +) + // DB is a wrap of sql.DB with extra contents type DB struct { *sql.DB diff --git a/core/interface.go b/core/interface.go new file mode 100644 index 00000000..a5c8e4e2 --- /dev/null +++ b/core/interface.go @@ -0,0 +1,22 @@ +package core + +import ( + "context" + "database/sql" +) + +// Queryer represents an interface to query a SQL to get data from database +type Queryer interface { + QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) +} + +// Executer represents an interface to execute a SQL +type Executer interface { + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) +} + +// QueryExecuter combines the Queryer and Executer +type QueryExecuter interface { + Queryer + Executer +} diff --git a/core/stmt.go b/core/stmt.go index 9d5954bd..4b1c7605 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -27,7 +27,7 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { var i int query = re.ReplaceAllStringFunc(query, func(src string) string { names[src[1:]] = i - i += 1 + i++ return "?" }) diff --git a/core/tx.go b/core/tx.go index 07713267..99a8097d 100644 --- a/core/tx.go +++ b/core/tx.go @@ -12,6 +12,11 @@ import ( "xorm.io/xorm/log" ) +var ( + _ QueryExecuter = &Tx{} +) + +// Tx represents a transaction type Tx struct { *sql.Tx db *DB @@ -50,7 +55,7 @@ func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { var i int query = re.ReplaceAllStringFunc(query, func(src string) string { names[src[1:]] = i - i += 1 + i++ return "?" }) diff --git a/dialects/dialect.go b/dialects/dialect.go index 4fdf35e9..a3328e05 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -39,9 +39,8 @@ func (uri *URI) SetSchema(schema string) { // Dialect represents a kind of database type Dialect interface { - Init(*core.DB, *URI) error + Init(*URI) error URI() *URI - DB() *core.DB SQLType(*schemas.Column) string FormatBytes(b []byte) string DefaultSchema() string @@ -52,18 +51,18 @@ type Dialect interface { AutoIncrStr() string - GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) + GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) IndexCheckSQL(tableName, idxName string) (string, []interface{}) CreateIndexSQL(tableName string, index *schemas.Index) string DropIndexSQL(tableName string, index *schemas.Index) string - GetTables(ctx context.Context) ([]*schemas.Table, error) - IsTableExist(ctx context.Context, tableName string) (bool, error) + 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) DropTableSQL(tableName string) (string, bool) - GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) - IsColumnExist(ctx context.Context, tableName string, colName string) (bool, 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 ModifyColumnSQL(tableName string, col *schemas.Column) string @@ -75,7 +74,6 @@ type Dialect interface { // Base represents a basic dialect and all real dialects could embed this struct type Base struct { - db *core.DB dialect Dialect uri *URI quoter schemas.Quoter @@ -85,16 +83,12 @@ func (b *Base) Quoter() schemas.Quoter { return b.quoter } -func (b *Base) DB() *core.DB { - return b.db -} - func (b *Base) DefaultSchema() string { return "" } -func (b *Base) Init(db *core.DB, dialect Dialect, uri *URI) error { - b.db, b.dialect, b.uri = db, dialect, uri +func (b *Base) Init(dialect Dialect, uri *URI) error { + b.dialect, b.uri = dialect, uri return nil } @@ -160,8 +154,8 @@ func (db *Base) DropTableSQL(tableName string) (string, bool) { return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)), true } -func (db *Base) HasRecords(ctx context.Context, query string, args ...interface{}) (bool, error) { - rows, err := db.DB().QueryContext(ctx, query, args...) +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 { return false, err } @@ -173,7 +167,7 @@ func (db *Base) HasRecords(ctx context.Context, query string, args ...interface{ return false, nil } -func (db *Base) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { +func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { quote := db.dialect.Quoter().Quote query := fmt.Sprintf( "SELECT %v FROM %v.%v WHERE %v = ? AND %v = ? AND %v = ?", @@ -184,7 +178,7 @@ func (db *Base) IsColumnExist(ctx context.Context, tableName, colName string) (b quote("TABLE_NAME"), quote("COLUMN_NAME"), ) - return db.HasRecords(ctx, query, db.uri.DBName, tableName, colName) + return db.HasRecords(queryer, ctx, query, db.uri.DBName, tableName, colName) } func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { diff --git a/dialects/driver.go b/dialects/driver.go index 89d21bfc..ae3afe42 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -6,8 +6,6 @@ package dialects import ( "fmt" - - "xorm.io/xorm/core" ) type Driver interface { @@ -53,11 +51,7 @@ func OpenDialect(driverName, connstr string) (Dialect, error) { return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DBType) } - db, err := core.Open(driverName, connstr) - if err != nil { - return nil, err - } - dialect.Init(db, uri) + dialect.Init(uri) return dialect, nil } diff --git a/dialects/mssql.go b/dialects/mssql.go index dd3f4247..92457ff9 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -212,9 +212,9 @@ type mssql struct { Base } -func (db *mssql) Init(d *core.DB, uri *URI) error { +func (db *mssql) Init(uri *URI) error { db.quoter = mssqlQuoter - return db.Base.Init(d, db, uri) + return db.Base.Init(db, uri) } func (db *mssql) SQLType(c *schemas.Column) string { @@ -319,18 +319,18 @@ func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{} return sql, args } -func (db *mssql) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { +func (db *mssql) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { query := `SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "COLUMN_NAME" = ?` - return db.HasRecords(ctx, query, tableName, colName) + return db.HasRecords(queryer, ctx, query, tableName, colName) } -func (db *mssql) IsTableExist(ctx context.Context, tableName string) (bool, error) { +func (db *mssql) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { sql := "select * from sysobjects where id = object_id(N'" + tableName + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1" - return db.HasRecords(ctx, sql) + return db.HasRecords(queryer, ctx, sql) } -func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{} s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, "default_is_null" = (CASE WHEN c.text is null THEN 1 ELSE 0 END), @@ -346,7 +346,7 @@ func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, ma ) as p on p.object_id = a.object_id AND p.column_id = a.column_id where a.object_id=object_id('` + tableName + `')` - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -401,11 +401,11 @@ func (db *mssql) GetColumns(ctx context.Context, tableName string) ([]string, ma return colSeq, cols, nil } -func (db *mssql) GetTables(ctx context.Context) ([]*schemas.Table, error) { +func (db *mssql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := `select name from sysobjects where xtype ='U'` - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -425,7 +425,7 @@ func (db *mssql) GetTables(ctx context.Context) ([]*schemas.Table, error) { return tables, nil } -func (db *mssql) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { +func (db *mssql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := `SELECT IXS.NAME AS [INDEX_NAME], @@ -439,7 +439,7 @@ AND IXCS.COLUMN_ID=C.COLUMN_ID WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? ` - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/dialects/mysql.go b/dialects/mysql.go index b7598680..289e0ec7 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -179,9 +179,9 @@ type mysql struct { rowFormat string } -func (db *mysql) Init(d *core.DB, uri *URI) error { +func (db *mysql) Init(uri *URI) error { db.quoter = mysqlQuoter - return db.Base.Init(d, db, uri) + return db.Base.Init(db, uri) } func (db *mysql) SetParams(params map[string]string) { @@ -286,9 +286,9 @@ func (db *mysql) IndexCheckSQL(tableName, idxName string) (string, []interface{} return sql, args } -func (db *mysql) IsTableExist(ctx context.Context, tableName string) (bool, error) { +func (db *mysql) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" - return db.HasRecords(ctx, sql, db.uri.DBName, tableName) + return db.HasRecords(queryer, ctx, sql, db.uri.DBName, tableName) } func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { @@ -301,12 +301,12 @@ func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { return sql } -func (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{db.uri.DBName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -411,12 +411,12 @@ func (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, ma return colSeq, cols, nil } -func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) { +func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{db.uri.DBName} s := "SELECT `TABLE_NAME`, `ENGINE`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -459,11 +459,11 @@ func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) { } } -func (db *mysql) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { +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` = ?" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/dialects/oracle.go b/dialects/oracle.go index 466b6a45..db823e95 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -506,9 +506,9 @@ type oracle struct { Base } -func (db *oracle) Init(d *core.DB, uri *URI) error { +func (db *oracle) Init(uri *URI) error { db.quoter = oracleQuoter - return db.Base.Init(d, db, uri) + return db.Base.Init(db, uri) } func (db *oracle) SQLType(c *schemas.Column) string { @@ -611,23 +611,23 @@ func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{ `WHERE TABLE_NAME = :1 AND INDEX_NAME = :2`, args } -func (db *oracle) IsTableExist(ctx context.Context, tableName string) (bool, error) { - return db.HasRecords(ctx, `SELECT table_name FROM user_tables WHERE table_name = :1`, tableName) +func (db *oracle) 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 = :1`, tableName) } -func (db *oracle) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { +func (db *oracle) 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 = :1" + " AND column_name = :2" - return db.HasRecords(ctx, query, args...) + return db.HasRecords(queryer, ctx, query, args...) } -func (db *oracle) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -719,11 +719,11 @@ func (db *oracle) GetColumns(ctx context.Context, tableName string) ([]string, m return colSeq, cols, nil } -func (db *oracle) GetTables(ctx context.Context) ([]*schemas.Table, error) { +func (db *oracle) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT table_name FROM user_tables" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -742,12 +742,12 @@ func (db *oracle) GetTables(ctx context.Context) ([]*schemas.Table, error) { return tables, nil } -func (db *oracle) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { +func (db *oracle) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{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 =:1" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/dialects/postgres.go b/dialects/postgres.go index 0a851fe2..a83c3a5c 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -776,9 +776,9 @@ type postgres struct { Base } -func (db *postgres) Init(d *core.DB, uri *URI) error { +func (db *postgres) Init(uri *URI) error { db.quoter = postgresQuoter - err := db.Base.Init(d, db, uri) + err := db.Base.Init(db, uri) if err != nil { return err } @@ -942,12 +942,12 @@ func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interfac `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } -func (db *postgres) IsTableExist(ctx context.Context, tableName string) (bool, error) { +func (db *postgres) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { if len(db.uri.Schema) == 0 { - return db.HasRecords(ctx, `SELECT tablename FROM pg_tables WHERE tablename = $1`, tableName) + return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE tablename = $1`, tableName) } - return db.HasRecords(ctx, `SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = $2`, + return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = $2`, db.uri.Schema, tableName) } @@ -980,7 +980,7 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName)) } -func (db *postgres) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { +func (db *postgres) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { args := []interface{}{db.uri.Schema, tableName, colName} query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" + " AND column_name = $3" @@ -990,7 +990,7 @@ func (db *postgres) IsColumnExist(ctx context.Context, tableName, colName string " AND column_name = $2" } - rows, err := db.DB().QueryContext(ctx, query, args...) + rows, err := queryer.QueryContext(ctx, query, args...) if err != nil { return false, err } @@ -999,7 +999,7 @@ func (db *postgres) IsColumnExist(ctx context.Context, tableName, colName string return rows.Next(), nil } -func (db *postgres) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *postgres) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{db.uri.Schema, tableName, db.uri.Schema} s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, @@ -1013,7 +1013,7 @@ FROM pg_attribute f LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name WHERE n.nspname= $1 AND c.relkind = 'r'::char AND c.relname = $2 AND s.table_schema = $3 AND f.attnum > 0 ORDER BY f.attnum;` - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -1132,7 +1132,7 @@ WHERE n.nspname= $1 AND c.relkind = 'r'::char AND c.relname = $2 AND s.table_sch return colSeq, cols, nil } -func (db *postgres) GetTables(ctx context.Context) ([]*schemas.Table, error) { +func (db *postgres) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT tablename FROM pg_tables" if len(db.uri.Schema) != 0 { @@ -1140,7 +1140,7 @@ func (db *postgres) GetTables(ctx context.Context) ([]*schemas.Table, error) { s = s + " WHERE schemaname = $1" } - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -1171,7 +1171,7 @@ func getIndexColName(indexdef string) []string { return colNames } -func (db *postgres) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { +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") if len(db.uri.Schema) != 0 { @@ -1179,7 +1179,7 @@ func (db *postgres) GetIndexes(ctx context.Context, tableName string) (map[strin s = s + " AND schemaname=$2" } - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 710babe6..0e95ebc7 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -151,9 +151,9 @@ type sqlite3 struct { Base } -func (db *sqlite3) Init(d *core.DB, uri *URI) error { +func (db *sqlite3) Init(uri *URI) error { db.quoter = sqlite3Quoter - return db.Base.Init(d, db, uri) + return db.Base.Init(db, uri) } func (db *sqlite3) SetQuotePolicy(quotePolicy QuotePolicy) { @@ -225,8 +225,8 @@ func (db *sqlite3) IndexCheckSQL(tableName, idxName string) (string, []interface return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args } -func (db *sqlite3) IsTableExist(ctx context.Context, tableName string) (bool, error) { - return db.HasRecords(ctx, "SELECT name FROM sqlite_master WHERE type='table' and name = ?", tableName) +func (db *sqlite3) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { + return db.HasRecords(queryer, ctx, "SELECT name FROM sqlite_master WHERE type='table' and name = ?", tableName) } func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { @@ -286,9 +286,9 @@ func (db *sqlite3) ForUpdateSQL(query string) string { return query } -func (db *sqlite3) IsColumnExist(ctx context.Context, tableName, colName string) (bool, error) { +func (db *sqlite3) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { query := "SELECT * FROM " + tableName + " LIMIT 0" - rows, err := db.DB().QueryContext(ctx, query) + rows, err := queryer.QueryContext(ctx, query) if err != nil { return false, err } @@ -370,11 +370,11 @@ func parseString(colStr string) (*schemas.Column, error) { return col, nil } -func (db *sqlite3) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *sqlite3) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -427,11 +427,11 @@ func (db *sqlite3) GetColumns(ctx context.Context, tableName string) ([]string, return colSeq, cols, nil } -func (db *sqlite3) GetTables(ctx context.Context) ([]*schemas.Table, error) { +func (db *sqlite3) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT name FROM sqlite_master WHERE type='table'" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -452,11 +452,11 @@ func (db *sqlite3) GetTables(ctx context.Context) ([]*schemas.Table, error) { return tables, nil } -func (db *sqlite3) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) { +func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" - rows, err := db.DB().QueryContext(ctx, s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } diff --git a/engine.go b/engine.go index 4694e1c0..dd34fe1b 100644 --- a/engine.go +++ b/engine.go @@ -35,6 +35,7 @@ type Engine struct { engineGroup *EngineGroup logger log.ContextLogger tagParser *tags.Parser + db *core.DB driverName string dataSourceName string @@ -211,7 +212,7 @@ func (engine *Engine) NewDB() (*core.DB, error) { // DB return the wrapper of sql.DB func (engine *Engine) DB() *core.DB { - return engine.dialect.DB() + return engine.db } // Dialect return database dialect @@ -267,14 +268,14 @@ func (engine *Engine) NoAutoCondition(no ...bool) *Session { } func (engine *Engine) loadTableInfo(table *schemas.Table) error { - colSeq, cols, err := engine.dialect.GetColumns(engine.defaultContext, table.Name) + colSeq, cols, err := engine.dialect.GetColumns(engine.db, engine.defaultContext, table.Name) if err != nil { return err } for _, name := range colSeq { table.AddColumn(cols[name]) } - indexes, err := engine.dialect.GetIndexes(engine.defaultContext, table.Name) + indexes, err := engine.dialect.GetIndexes(engine.db, engine.defaultContext, table.Name) if err != nil { return err } @@ -301,7 +302,7 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { // DBMetas Retrieve all tables, columns, indexes' informations from database. func (engine *Engine) DBMetas() ([]*schemas.Table, error) { - tables, err := engine.dialect.GetTables(engine.defaultContext) + tables, err := engine.dialect.GetTables(engine.db, engine.defaultContext) if err != nil { return nil, err } @@ -361,7 +362,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch uri := engine.dialect.URI() destURI := *uri - dstDialect.Init(nil, &destURI) + dstDialect.Init(&destURI) } _, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm %s, from %s to %s*/\n\n", @@ -911,7 +912,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } else { for _, col := range table.Columns() { - isExist, err := engine.dialect.IsColumnExist(session.ctx, tableNameNoSchema, col.Name) + isExist, err := engine.dialect.IsColumnExist(engine.db, session.ctx, tableNameNoSchema, col.Name) if err != nil { return err } diff --git a/engine_group.go b/engine_group.go index 868d4dc9..d557645e 100644 --- a/engine_group.go +++ b/engine_group.go @@ -161,17 +161,17 @@ func (eg *EngineGroup) SetMapper(mapper names.Mapper) { // SetMaxIdleConns set the max idle connections on pool, default is 2 func (eg *EngineGroup) SetMaxIdleConns(conns int) { - eg.Engine.dialect.DB().SetMaxIdleConns(conns) + eg.Engine.DB().SetMaxIdleConns(conns) for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].dialect.DB().SetMaxIdleConns(conns) + eg.slaves[i].DB().SetMaxIdleConns(conns) } } // SetMaxOpenConns is only available for go 1.2+ func (eg *EngineGroup) SetMaxOpenConns(conns int) { - eg.Engine.dialect.DB().SetMaxOpenConns(conns) + eg.Engine.DB().SetMaxOpenConns(conns) for i := 0; i < len(eg.slaves); i++ { - eg.slaves[i].dialect.DB().SetMaxOpenConns(conns) + eg.slaves[i].DB().SetMaxOpenConns(conns) } } diff --git a/session.go b/session.go index 4842883b..6b8bfbaf 100644 --- a/session.go +++ b/session.go @@ -99,7 +99,7 @@ func (session *Session) Init() { session.engine.tagParser, session.engine.DatabaseTZ, ) - + session.db = session.engine.db session.isAutoCommit = true session.isCommitedOrRollbacked = false session.isAutoClose = false @@ -140,6 +140,13 @@ func (session *Session) Close() { } } +func (session *Session) getQueryer() core.Queryer { + if session.tx != nil { + return session.tx + } + return session.db +} + // ContextCache enable context cache or not func (session *Session) ContextCache(context contexts.ContextCache) *Session { session.statement.SetContextCache(context) diff --git a/session_schema.go b/session_schema.go index 84eb586e..9ccf8abe 100644 --- a/session_schema.go +++ b/session_schema.go @@ -134,7 +134,7 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { tableName := session.engine.TableName(beanOrTableName) sqlStr, checkIfExist := session.engine.dialect.DropTableSQL(session.engine.TableName(tableName, true)) if !checkIfExist { - exist, err := session.engine.dialect.IsTableExist(session.ctx, tableName) + exist, err := session.engine.dialect.IsTableExist(session.getQueryer(), session.ctx, tableName) if err != nil { return err } @@ -160,7 +160,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) } func (session *Session) isTableExist(tableName string) (bool, error) { - return session.engine.dialect.IsTableExist(session.ctx, tableName) + return session.engine.dialect.IsTableExist(session.getQueryer(), session.ctx, tableName) } // IsTableEmpty if table have any records @@ -187,7 +187,7 @@ func (session *Session) isTableEmpty(tableName string) (bool, error) { // 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.ctx, tableName) + indexes, err := session.engine.dialect.GetIndexes(session.getQueryer(), session.ctx, tableName) if err != nil { return false, err } @@ -233,7 +233,7 @@ func (session *Session) Sync2(beans ...interface{}) error { defer session.Close() } - tables, err := engine.dialect.GetTables(session.ctx) + tables, err := engine.dialect.GetTables(session.getQueryer(), session.ctx) if err != nil { return err } diff --git a/xorm.go b/xorm.go index 2025522f..e9cd7415 100644 --- a/xorm.go +++ b/xorm.go @@ -13,6 +13,7 @@ import ( "time" "xorm.io/xorm/caches" + "xorm.io/xorm/core" "xorm.io/xorm/dialects" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -32,6 +33,11 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { return nil, err } + db, err := core.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + cacherMgr := caches.NewManager() mapper := names.NewCacheMapper(new(names.SnakeMapper)) tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) @@ -44,6 +50,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { tagParser: tagParser, driverName: driverName, dataSourceName: dataSourceName, + db: db, } if dialect.URI().DBType == schemas.SQLITE { From 9dca7f0703fae481fcfdd24c9c1e8e6ce8d4a06c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Mar 2020 02:19:56 +0000 Subject: [PATCH 085/112] Add more tests for FindAndCount (#1621) Add more tests for FindAndCount Reviewed-on: https://gitea.com/xorm/xorm/pulls/1621 --- session_find_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/session_find_test.go b/session_find_test.go index 3c8860c5..17364e67 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -553,6 +553,70 @@ func TestFindAndCountOneFuncWithDeleted(t *testing.T) { assert.EqualValues(t, 0, cnt) } +func TestFindAndCount2(t *testing.T) { + // User + type TestFindAndCountUser struct { + Id int64 `xorm:"bigint(11) pk autoincr"` + Name string `xorm:"'name'"` + } + + // Hotel + type TestFindAndCountHotel struct { + Id int64 `xorm:"bigint(11) pk autoincr"` + Name string `xorm:"'name'"` + Code string `xorm:"'code'"` + Region string `xorm:"'region'"` + CreateBy *TestFindAndCountUser `xorm:"'create_by'"` + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(TestFindAndCountUser), new(TestFindAndCountHotel)) + + var u = TestFindAndCountUser{ + Name: "myname", + } + cnt, err := testEngine.Insert(&u) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var hotel = TestFindAndCountHotel{ + Name: "myhotel", + Code: "111", + Region: "222", + CreateBy: &u, + } + cnt, err = testEngine.Insert(&hotel) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + hotels := make([]*TestFindAndCountHotel, 0) + cnt, err = testEngine. + Alias("t"). + Limit(10, 0). + FindAndCount(&hotels) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + hotels = make([]*TestFindAndCountHotel, 0) + cnt, err = testEngine. + Table(new(TestFindAndCountHotel)). + Alias("t"). + Limit(10, 0). + FindAndCount(&hotels) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + hotels = make([]*TestFindAndCountHotel, 0) + cnt, err = testEngine. + Table(new(TestFindAndCountHotel)). + Alias("t"). + Where("t.region like '6501%'"). + Limit(10, 0). + FindAndCount(&hotels) + assert.NoError(t, err) + assert.EqualValues(t, 0, cnt) +} + type FindMapDevice struct { Deviceid string `xorm:"pk"` Status int From 6132eea08c596bdcfb2d7aac7209d9cf61776ebc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Mar 2020 02:51:37 +0000 Subject: [PATCH 086/112] Fix dump test (#1625) Fix dump test Reviewed-on: https://gitea.com/xorm/xorm/pulls/1625 --- engine_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/engine_test.go b/engine_test.go index d6da5f33..b691d909 100644 --- a/engine_test.go +++ b/engine_test.go @@ -85,7 +85,7 @@ func TestDump(t *testing.T) { {Name: "5'\n"}, }) - fp := testEngine.Dialect().URI().DBName + ".sql" + fp := fmt.Sprintf("%v.sql", testEngine.Dialect().URI().DBType) os.Remove(fp) assert.NoError(t, testEngine.DumpAllToFile(fp)) @@ -99,8 +99,9 @@ func TestDump(t *testing.T) { assert.NoError(t, sess.Commit()) for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} { - t.Run(fmt.Sprintf("dump_%v", tp), func(t *testing.T) { - assert.NoError(t, testEngine.DumpAllToFile(fp, tp)) + name := fmt.Sprintf("dump_%v.sql", tp) + t.Run(name, func(t *testing.T) { + assert.NoError(t, testEngine.DumpAllToFile(name, tp)) }) } } From 0a3685be83181070a3271c6281eea18df14b7dc4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Mar 2020 09:36:45 +0000 Subject: [PATCH 087/112] Fix postgres schema problem (#1624) Fix postgres Add DefaultPostgresSchema back force push Fix postgres schema problem Reviewed-on: https://gitea.com/xorm/xorm/pulls/1624 --- dialects/dialect.go | 8 +--- dialects/postgres.go | 83 ++++++++++++++++++++++++++++-------------- dialects/table_name.go | 1 - 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index a3328e05..3c98a4a5 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -32,8 +32,9 @@ type URI struct { // SetSchema set schema func (uri *URI) SetSchema(schema string) { + // hack me if uri.DBType == schemas.POSTGRES { - uri.Schema = schema + uri.Schema = strings.TrimSpace(schema) } } @@ -43,7 +44,6 @@ type Dialect interface { URI() *URI SQLType(*schemas.Column) string FormatBytes(b []byte) string - DefaultSchema() string IsReserved(string) bool Quoter() schemas.Quoter @@ -83,10 +83,6 @@ func (b *Base) Quoter() schemas.Quoter { return b.quoter } -func (b *Base) DefaultSchema() string { - return "" -} - func (b *Base) Init(dialect Dialect, uri *URI) error { b.dialect, b.uri = dialect, uri return nil diff --git a/dialects/postgres.go b/dialects/postgres.go index a83c3a5c..16213c76 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -770,7 +770,10 @@ var ( postgresQuoter = schemas.Quoter{'"', '"', schemas.AlwaysReserve} ) -const postgresPublicSchema = "public" +var ( + // DefaultPostgresSchema default postgres schema + DefaultPostgresSchema = "public" +) type postgres struct { Base @@ -778,14 +781,14 @@ type postgres struct { func (db *postgres) Init(uri *URI) error { db.quoter = postgresQuoter - err := db.Base.Init(db, uri) - if err != nil { - return err + return db.Base.Init(db, uri) +} + +func (db *postgres) getSchema() string { + if db.uri.Schema != "" { + return db.uri.Schema } - if db.uri.Schema == "" { - db.uri.Schema = postgresPublicSchema - } - return nil + return DefaultPostgresSchema } func (db *postgres) needQuote(name string) bool { @@ -817,10 +820,6 @@ func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { } } -func (db *postgres) DefaultSchema() string { - return postgresPublicSchema -} - func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -932,32 +931,32 @@ func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) ([]st } func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { - if len(db.uri.Schema) == 0 { + if len(db.getSchema()) == 0 { args := []interface{}{tableName, idxName} return `SELECT indexname FROM pg_indexes WHERE tablename = ? AND indexname = ?`, args } - args := []interface{}{db.uri.Schema, tableName, idxName} + args := []interface{}{db.getSchema(), tableName, idxName} return `SELECT indexname FROM pg_indexes ` + `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } func (db *postgres) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { - if len(db.uri.Schema) == 0 { + if len(db.getSchema()) == 0 { return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE tablename = $1`, tableName) } return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = $2`, - db.uri.Schema, tableName) + db.getSchema(), tableName) } func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { - if len(db.uri.Schema) == 0 || strings.Contains(tableName, ".") { + 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)) } return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s", - db.uri.Schema, tableName, col.Name, db.SQLType(col)) + db.getSchema(), tableName, col.Name, db.SQLType(col)) } func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string { @@ -974,17 +973,17 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } } - if db.uri.Schema != "" { - idxName = db.uri.Schema + "." + idxName + if db.getSchema() != "" { + idxName = db.getSchema() + "." + idxName } return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName)) } func (db *postgres) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { - args := []interface{}{db.uri.Schema, tableName, colName} + args := []interface{}{db.getSchema(), tableName, colName} query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" + " AND column_name = $3" - if len(db.uri.Schema) == 0 { + if len(db.getSchema()) == 0 { args = []interface{}{tableName, colName} query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + " AND column_name = $2" @@ -1000,7 +999,7 @@ 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{}{db.uri.Schema, tableName, db.uri.Schema} + args := []interface{}{tableName} s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey @@ -1011,7 +1010,15 @@ FROM pg_attribute f 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 LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name -WHERE n.nspname= $1 AND c.relkind = 'r'::char AND c.relname = $2 AND s.table_schema = $3 AND f.attnum > 0 ORDER BY f.attnum;` +WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;` + + schema := db.getSchema() + if schema != "" { + s = fmt.Sprintf(s, "AND s.table_schema = $2") + args = append(args, schema) + } else { + s = fmt.Sprintf(s, "") + } rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { @@ -1135,8 +1142,9 @@ WHERE n.nspname= $1 AND c.relkind = 'r'::char AND c.relname = $2 AND s.table_sch func (db *postgres) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT tablename FROM pg_tables" - if len(db.uri.Schema) != 0 { - args = append(args, db.uri.Schema) + schema := db.getSchema() + if schema != "" { + args = append(args, schema) s = s + " WHERE schemaname = $1" } @@ -1174,8 +1182,8 @@ 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") - if len(db.uri.Schema) != 0 { - args = append(args, db.uri.Schema) + if len(db.getSchema()) != 0 { + args = append(args, db.getSchema()) s = s + " AND schemaname=$2" } @@ -1319,3 +1327,22 @@ func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*URI, error) { } return pgx.pqDriver.Parse(driverName, dataSourceName) } + +// QueryDefaultPostgresSchema returns the default postgres schema +func QueryDefaultPostgresSchema(ctx context.Context, queryer core.Queryer) (string, error) { + rows, err := queryer.QueryContext(ctx, "SHOW SEARCH_PATH") + if err != nil { + return "", err + } + defer rows.Close() + if rows.Next() { + var defaultSchema string + if err = rows.Scan(&defaultSchema); err != nil { + return "", err + } + parts := strings.Split(defaultSchema, ",") + return strings.TrimSpace(parts[len(parts)-1]), nil + } + + return "", errors.New("No default schema") +} diff --git a/dialects/table_name.go b/dialects/table_name.go index a989b386..e190cd4b 100644 --- a/dialects/table_name.go +++ b/dialects/table_name.go @@ -18,7 +18,6 @@ func TableNameWithSchema(dialect Dialect, tableName string) string { // Add schema name as prefix of table name. // Only for postgres database. if dialect.URI().Schema != "" && - dialect.URI().Schema != dialect.DefaultSchema() && strings.Index(tableName, ".") == -1 { return fmt.Sprintf("%s.%s", dialect.URI().Schema, tableName) } From 34f5bc3465fc1cfe4bdf94a7090d9ae100e0c139 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Mar 2020 12:28:11 +0000 Subject: [PATCH 088/112] Fix quote with blank (#1626) Fix quote with blank Reviewed-on: https://gitea.com/xorm/xorm/pulls/1626 --- schemas/quote.go | 10 +++++++--- schemas/quote_test.go | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/schemas/quote.go b/schemas/quote.go index 2a03152e..c44abe25 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -107,16 +107,17 @@ func findStart(value string, start int) int { return start } - var k int + var k = -1 for j := start; j < len(value); j++ { if value[j] != ' ' { k = j break } } - if k-1 == len(value) { + if k == -1 { return len(value) } + if (value[k] == 'A' || value[k] == 'a') && (value[k+1] == 'S' || value[k+1] == 's') { k = k + 2 } @@ -178,8 +179,11 @@ func (q Quoter) QuoteTo(buf *strings.Builder, value string) error { return err } } - var nextEnd = findWord(value, start) + if start == len(value) { + return nil + } + var nextEnd = findWord(value, start) if err := q.quoteWordTo(buf, value[start:nextEnd]); err != nil { return err } diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 7a43bd24..708b450e 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -32,6 +32,12 @@ func TestAlwaysQuoteTo(t *testing.T) { {`["myschema].[mytable"]`, `"myschema.mytable"`}, {"[message_user] AS [sender]", "`message_user` AS `sender`"}, {"[myschema].[mytable] AS [table]", "myschema.mytable AS table"}, + {" [mytable]", " mytable"}, + {" [mytable]", " mytable"}, + {"[mytable] ", "mytable "}, + {"[mytable] ", "mytable "}, + {" [mytable] ", " mytable "}, + {" [mytable] ", " mytable "}, } ) From 3e4dc66c5892d72f732a79d68fe6f9b3c29a33d0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Mar 2020 13:13:09 +0000 Subject: [PATCH 089/112] Add changelog for v1.0.1 (#1629) Add changelog for v1.0.1 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1629 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c59d57..8cd081e0 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.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25 + +* BUGFIXES + * Oracle : Local Naming Method (#1515) + * Fix find and count bug (#1618) + * Fix duplicated deleted condition on FindAndCount (#1619) + * Fix find and count bug with cache (#1622) + * Fix postgres schema problem (#1624) + * Fix quote with blank (#1626) + ## [1.0.0](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1242) - 2020-03-22 * BREAKING From fb8eb29d1eaf121d96279fd08c91d65161c848ed Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 26 Mar 2020 04:41:02 +0000 Subject: [PATCH 090/112] Fix lint errors (#1631) Fix lint errors Reviewed-on: https://gitea.com/xorm/xorm/pulls/1631 --- convert.go | 7 +++---- engine.go | 3 +++ engine_group.go | 1 + engine_group_policy.go | 2 ++ processors_test.go | 20 ++++++++++---------- session_insert_test.go | 30 +++++++++++++++--------------- tags_test.go | 4 ++-- time_test.go | 22 +++++++++++----------- types_test.go | 11 +++++------ 9 files changed, 52 insertions(+), 48 deletions(-) diff --git a/convert.go b/convert.go index 05db2704..90a9059e 100644 --- a/convert.go +++ b/convert.go @@ -25,11 +25,10 @@ func strconvErr(err error) error { func cloneBytes(b []byte) []byte { if b == nil { return nil - } else { - c := make([]byte, len(b)) - copy(c, b) - return c } + c := make([]byte, len(b)) + copy(c, b) + return c } func asString(src interface{}) string { diff --git a/engine.go b/engine.go index dd34fe1b..49124309 100644 --- a/engine.go +++ b/engine.go @@ -44,14 +44,17 @@ type Engine struct { DatabaseTZ *time.Location // The timezone of the database } +// SetCacher sets cacher for the table func (engine *Engine) SetCacher(tableName string, cacher caches.Cacher) { engine.cacherMgr.SetCacher(tableName, cacher) } +// GetCacher returns the cachher of the special table func (engine *Engine) GetCacher(tableName string) caches.Cacher { return engine.cacherMgr.GetCacher(tableName) } +// SetQuotePolicy sets the special quote policy func (engine *Engine) SetQuotePolicy(quotePolicy dialects.QuotePolicy) { engine.dialect.SetQuotePolicy(quotePolicy) } diff --git a/engine_group.go b/engine_group.go index d557645e..02a57ab4 100644 --- a/engine_group.go +++ b/engine_group.go @@ -181,6 +181,7 @@ func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup { return eg } +// SetQuotePolicy sets the special quote policy func (eg *EngineGroup) SetQuotePolicy(quotePolicy dialects.QuotePolicy) { eg.Engine.SetQuotePolicy(quotePolicy) for i := 0; i < len(eg.slaves); i++ { diff --git a/engine_group_policy.go b/engine_group_policy.go index 5b56e899..1def8ce4 100644 --- a/engine_group_policy.go +++ b/engine_group_policy.go @@ -51,6 +51,7 @@ func WeightRandomPolicy(weights []int) GroupPolicyHandler { } } +// RoundRobinPolicy returns a group policy handler func RoundRobinPolicy() GroupPolicyHandler { var pos = -1 var lock sync.Mutex @@ -68,6 +69,7 @@ func RoundRobinPolicy() GroupPolicyHandler { } } +// WeightRoundRobinPolicy returns a group policy handler func WeightRoundRobinPolicy(weights []int) GroupPolicyHandler { var rands = make([]int, 0, len(weights)) for i := 0; i < len(weights); i++ { diff --git a/processors_test.go b/processors_test.go index c0f962d3..21d4a7fb 100644 --- a/processors_test.go +++ b/processors_test.go @@ -485,7 +485,7 @@ func TestProcessorsTx(t *testing.T) { t.Error(errors.New("AfterInsertedViaExt is set")) } - insertedId := p2.Id + insertedID := p2.Id // -- // test update processors with tx rollback @@ -513,7 +513,7 @@ func TestProcessorsTx(t *testing.T) { p = p2 // reset - _, err = session.ID(insertedId).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) + _, err = session.ID(insertedID).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) assert.NoError(t, err) if p.B4UpdateFlag == 0 { @@ -548,7 +548,7 @@ func TestProcessorsTx(t *testing.T) { session.Close() p2 = &ProcessorsStruct{} - _, err = testEngine.ID(insertedId).Get(p2) + _, err = testEngine.ID(insertedID).Get(p2) assert.NoError(t, err) if p2.B4UpdateFlag != 0 { @@ -572,7 +572,7 @@ func TestProcessorsTx(t *testing.T) { err = session.Begin() assert.NoError(t, err) - p = &ProcessorsStruct{Id: insertedId} + p = &ProcessorsStruct{Id: insertedID} _, err = session.Update(p) assert.NoError(t, err) @@ -611,7 +611,7 @@ func TestProcessorsTx(t *testing.T) { p = &ProcessorsStruct{} - _, err = session.ID(insertedId).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) + _, err = session.ID(insertedID).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p) assert.NoError(t, err) if p.B4UpdateFlag == 0 { @@ -645,7 +645,7 @@ func TestProcessorsTx(t *testing.T) { session.Close() p2 = &ProcessorsStruct{} - _, err = testEngine.ID(insertedId).Get(p2) + _, err = testEngine.ID(insertedID).Get(p2) assert.NoError(t, err) if p.B4UpdateFlag == 0 { @@ -687,7 +687,7 @@ func TestProcessorsTx(t *testing.T) { p = &ProcessorsStruct{} // reset - _, err = session.ID(insertedId).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) + _, err = session.ID(insertedID).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) assert.NoError(t, err) if p.B4DeleteFlag == 0 { @@ -721,7 +721,7 @@ func TestProcessorsTx(t *testing.T) { session.Close() p2 = &ProcessorsStruct{} - _, err = testEngine.ID(insertedId).Get(p2) + _, err = testEngine.ID(insertedID).Get(p2) assert.NoError(t, err) if p2.B4DeleteFlag != 0 { @@ -747,7 +747,7 @@ func TestProcessorsTx(t *testing.T) { p = &ProcessorsStruct{} - _, err = session.ID(insertedId).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) + _, err = session.ID(insertedID).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p) assert.NoError(t, err) if p.B4DeleteFlag == 0 { @@ -788,7 +788,7 @@ func TestProcessorsTx(t *testing.T) { err = session.Begin() assert.NoError(t, err) - p = &ProcessorsStruct{Id: insertedId} + p = &ProcessorsStruct{Id: insertedID} _, err = session.Delete(p) assert.NoError(t, err) diff --git a/session_insert_test.go b/session_insert_test.go index 3bc2d5b1..5fded623 100644 --- a/session_insert_test.go +++ b/session_insert_test.go @@ -336,9 +336,9 @@ func TestInsertCreated(t *testing.T) { assert.EqualValues(t, ci6.Created.Unix(), di6.Created.Unix()) } -type JsonTime time.Time +type JSONTime time.Time -func (j JsonTime) format() string { +func (j JSONTime) format() string { t := time.Time(j) if t.IsZero() { return "" @@ -347,11 +347,11 @@ func (j JsonTime) format() string { return t.Format("2006-01-02") } -func (j JsonTime) MarshalText() ([]byte, error) { +func (j JSONTime) MarshalText() ([]byte, error) { return []byte(j.format()), nil } -func (j JsonTime) MarshalJSON() ([]byte, error) { +func (j JSONTime) MarshalJSON() ([]byte, error) { return []byte(`"` + j.format() + `"`), nil } @@ -359,40 +359,40 @@ func TestDefaultTime3(t *testing.T) { type PrepareTask struct { Id int `xorm:"not null pk autoincr INT(11)" json:"id"` // ... - StartTime JsonTime `xorm:"not null default '2006-01-02 15:04:05' TIMESTAMP index" json:"start_time"` - EndTime JsonTime `xorm:"not null default '2006-01-02 15:04:05' TIMESTAMP" json:"end_time"` + StartTime JSONTime `xorm:"not null default '2006-01-02 15:04:05' TIMESTAMP index" json:"start_time"` + EndTime JSONTime `xorm:"not null default '2006-01-02 15:04:05' TIMESTAMP" json:"end_time"` Cuser string `xorm:"not null default '' VARCHAR(64) index" json:"cuser"` Muser string `xorm:"not null default '' VARCHAR(64)" json:"muser"` - Ctime JsonTime `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created" json:"ctime"` - Mtime JsonTime `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP updated" json:"mtime"` + Ctime JSONTime `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created" json:"ctime"` + Mtime JSONTime `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP updated" json:"mtime"` } assert.NoError(t, prepareEngine()) assertSync(t, new(PrepareTask)) prepareTask := &PrepareTask{ - StartTime: JsonTime(time.Now()), + StartTime: JSONTime(time.Now()), Cuser: "userId", Muser: "userId", } - cnt, err := testEngine.Omit("end_time").InsertOne(prepareTask) + cnt, err := testEngine.Omit("end_time").Insert(prepareTask) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) } -type MyJsonTime struct { +type MyJSONTime struct { Id int64 `json:"id"` - Created JsonTime `xorm:"created" json:"created_at"` + Created JSONTime `xorm:"created" json:"created_at"` } func TestCreatedJsonTime(t *testing.T) { assert.NoError(t, prepareEngine()) - di5 := new(MyJsonTime) + di5 := new(MyJSONTime) err := testEngine.Sync2(di5) assert.NoError(t, err) - ci5 := &MyJsonTime{} + ci5 := &MyJSONTime{} _, err = testEngine.Insert(ci5) assert.NoError(t, err) @@ -401,7 +401,7 @@ func TestCreatedJsonTime(t *testing.T) { assert.True(t, has) assert.EqualValues(t, time.Time(ci5.Created).Unix(), time.Time(di5.Created).Unix()) - var dis = make([]MyJsonTime, 0) + var dis = make([]MyJSONTime, 0) err = testEngine.Find(&dis) assert.NoError(t, err) } diff --git a/tags_test.go b/tags_test.go index 74822956..c2a56290 100644 --- a/tags_test.go +++ b/tags_test.go @@ -686,7 +686,7 @@ func TestCreatedUpdated(t *testing.T) { assert.True(t, has) - c2.Value -= 1 + c2.Value-- _, err = testEngine.ID(c2.Id).Update(c2) assert.NoError(t, err) } @@ -714,7 +714,7 @@ func TestCreatedUpdatedInt64(t *testing.T) { assert.NoError(t, err) assert.True(t, has) - c2.Value -= 1 + c2.Value-- _, err = testEngine.ID(c2.Id).Update(c2) assert.NoError(t, err) } diff --git a/time_test.go b/time_test.go index 6a60f03c..609d7882 100644 --- a/time_test.go +++ b/time_test.go @@ -360,27 +360,27 @@ func TestTimeUserDeletedDiffLoc(t *testing.T) { fmt.Println("user3", user3.DeletedAt, user4.DeletedAt) } -type JsonDate time.Time +type JSONDate time.Time -func (j JsonDate) MarshalJSON() ([]byte, error) { +func (j JSONDate) MarshalJSON() ([]byte, error) { if time.Time(j).IsZero() { return []byte(`""`), nil } return []byte(`"` + time.Time(j).Format("2006-01-02 15:04:05") + `"`), nil } -func (j *JsonDate) UnmarshalJSON(value []byte) error { +func (j *JSONDate) UnmarshalJSON(value []byte) error { var v = strings.TrimSpace(strings.Trim(string(value), "\"")) t, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local) if err != nil { return err } - *j = JsonDate(t) + *j = JSONDate(t) return nil } -func (j *JsonDate) Unix() int64 { +func (j *JSONDate) Unix() int64 { return (*time.Time)(j).Unix() } @@ -389,9 +389,9 @@ func TestCustomTimeUserDeleted(t *testing.T) { type UserDeleted3 struct { Id string - CreatedAt JsonDate `xorm:"created"` - UpdatedAt JsonDate `xorm:"updated"` - DeletedAt JsonDate `xorm:"deleted"` + CreatedAt JSONDate `xorm:"created"` + UpdatedAt JSONDate `xorm:"updated"` + DeletedAt JSONDate `xorm:"deleted"` } assertSync(t, new(UserDeleted3)) @@ -442,9 +442,9 @@ func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { type UserDeleted4 struct { Id string - CreatedAt JsonDate `xorm:"created"` - UpdatedAt JsonDate `xorm:"updated"` - DeletedAt JsonDate `xorm:"deleted"` + CreatedAt JSONDate `xorm:"created"` + UpdatedAt JSONDate `xorm:"updated"` + DeletedAt JSONDate `xorm:"deleted"` } assertSync(t, new(UserDeleted4)) diff --git a/types_test.go b/types_test.go index 5cd01be2..7cabb10d 100644 --- a/types_test.go +++ b/types_test.go @@ -302,10 +302,10 @@ type Status struct { var ( _ convert.Conversion = &Status{} - Registered Status = Status{"Registered", "white"} - Approved Status = Status{"Approved", "green"} - Removed Status = Status{"Removed", "red"} - Statuses map[string]Status = map[string]Status{ + Registered = Status{"Registered", "white"} + Approved = Status{"Approved", "green"} + Removed = Status{"Removed", "red"} + Statuses = map[string]Status{ Registered.Name: Registered, Approved.Name: Approved, Removed.Name: Removed, @@ -316,9 +316,8 @@ func (s *Status) FromDB(bytes []byte) error { if r, ok := Statuses[string(bytes)]; ok { *s = r return nil - } else { - return errors.New("no this data") } + return errors.New("no this data") } func (s *Status) ToDB() ([]byte, error) { From 6254e7899f1bc0a3cd905100c8f048220960ecd0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 26 Mar 2020 12:01:28 +0000 Subject: [PATCH 091/112] Move column string to standalone method (#1633) Move column string to standalone method Reviewed-on: https://gitea.com/xorm/xorm/pulls/1633 --- dialects/dialect.go | 112 ++++++++++++++++++++++++------------------- dialects/mssql.go | 7 +-- dialects/mysql.go | 11 ++--- dialects/oracle.go | 3 +- dialects/postgres.go | 7 +-- dialects/sqlite3.go | 7 +-- 6 files changed, 76 insertions(+), 71 deletions(-) diff --git a/dialects/dialect.go b/dialects/dialect.go index 3c98a4a5..dc96f73a 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -96,51 +96,6 @@ func (b *Base) DBType() schemas.DBType { return b.uri.DBType } -// String generate column description string according dialect -func (b *Base) String(col *schemas.Column) string { - sql := b.dialect.Quoter().Quote(col.Name) + " " - - sql += b.dialect.SQLType(col) + " " - - if col.IsPrimaryKey { - sql += "PRIMARY KEY " - if col.IsAutoIncrement { - sql += b.dialect.AutoIncrStr() + " " - } - } - - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } - - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } - - return sql -} - -// StringNoPk generate column description string according dialect without primary keys -func (b *Base) StringNoPk(col *schemas.Column) string { - sql := b.dialect.Quoter().Quote(col.Name) + " " - - sql += b.dialect.SQLType(col) + " " - - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } - - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " - } - - return sql -} - func (b *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } @@ -178,8 +133,8 @@ func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableNa } func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { - return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), - db.String(col)) + s, _ := ColumnString(db.dialect, col, true) + return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), s) } func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { @@ -207,7 +162,8 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { } func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { - return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, db.StringNoPk(col)) + s, _ := ColumnString(db.dialect, col, false) + return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, s) } func (b *Base) ForUpdateSQL(query string) string { @@ -266,3 +222,63 @@ func regDrvsNDialects() bool { func init() { regDrvsNDialects() } + +// ColumnString generate column description string according dialect +func ColumnString(dialect Dialect, col *schemas.Column, includePrimaryKey bool) (string, error) { + bd := strings.Builder{} + + if err := dialect.Quoter().QuoteTo(&bd, col.Name); err != nil { + return "", err + } + + if err := bd.WriteByte(' '); err != nil { + return "", err + } + + if _, err := bd.WriteString(dialect.SQLType(col)); err != nil { + return "", err + } + + if err := bd.WriteByte(' '); err != nil { + return "", err + } + + if includePrimaryKey && col.IsPrimaryKey { + 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 col.Default != "" { + 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.Nullable { + if _, err := bd.WriteString("NULL "); err != nil { + return "", err + } + } else { + if _, err := bd.WriteString("NOT NULL "); err != nil { + return "", err + } + } + + return bd.String(), nil +} diff --git a/dialects/mssql.go b/dialects/mssql.go index 92457ff9..ae3f4afd 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -501,11 +501,8 @@ func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) ([]strin for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - if col.IsPrimaryKey && len(pkList) == 1 { - sql += db.String(col) - } else { - sql += db.StringNoPk(col) - } + s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) + sql += s sql = strings.TrimSpace(sql) sql += ", " } diff --git a/dialects/mysql.go b/dialects/mysql.go index 289e0ec7..b61bc7da 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -293,8 +293,8 @@ func (db *mysql) IsTableExist(queryer core.Queryer, ctx context.Context, tableNa func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { quoter := db.dialect.Quoter() - sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), - db.String(col)) + s, _ := ColumnString(db, col, true) + sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), s) if len(col.Comment) > 0 { sql += " COMMENT '" + col.Comment + "'" } @@ -525,11 +525,8 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]strin for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - if col.IsPrimaryKey && len(pkList) == 1 { - sql += db.String(col) - } else { - sql += db.StringNoPk(col) - } + s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) + sql += s sql = strings.TrimSpace(sql) if len(col.Comment) > 0 { sql += " COMMENT '" + col.Comment + "'" diff --git a/dialects/oracle.go b/dialects/oracle.go index db823e95..c8aaf97e 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -572,7 +572,8 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) ([]stri /*if col.IsPrimaryKey && len(pkList) == 1 { sql += col.String(b.dialect) } else {*/ - sql += db.StringNoPk(col) + s, _ := ColumnString(db, col, false) + sql += s // } sql = strings.TrimSpace(sql) sql += ", " diff --git a/dialects/postgres.go b/dialects/postgres.go index 16213c76..ac64c093 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -908,11 +908,8 @@ func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) ([]st for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - if col.IsPrimaryKey && len(pkList) == 1 { - sql += db.String(col) - } else { - sql += db.StringNoPk(col) - } + s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) + sql += s sql = strings.TrimSpace(sql) sql += ", " } diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 0e95ebc7..812a3698 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -260,11 +260,8 @@ func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) ([]str for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - if col.IsPrimaryKey && len(pkList) == 1 { - sql += db.String(col) - } else { - sql += db.StringNoPk(col) - } + s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) + sql += s sql = strings.TrimSpace(sql) sql += ", " } From b78418daa5d73249f7d9b461c3e502806a7d16d9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 26 Mar 2020 12:31:08 +0000 Subject: [PATCH 092/112] Support session id (#1632) small nit Support session id Reviewed-on: https://gitea.com/xorm/xorm/pulls/1632 --- core/db.go | 2 +- engine.go | 7 +++++++ log/logger_context.go | 28 ++++++++++++++++++++++++++-- session.go | 30 +++++++++++++++++++++++++----- xorm.go | 1 + 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/core/db.go b/core/db.go index 6845565d..671d1dc2 100644 --- a/core/db.go +++ b/core/db.go @@ -118,7 +118,7 @@ func (db *DB) NeedLogSQL(ctx context.Context) bool { return false } - v := ctx.Value("__xorm_show_sql") + v := ctx.Value(log.SessionShowSQLKey) if showSQL, ok := v.(bool); ok { return showSQL } diff --git a/engine.go b/engine.go index 49124309..80baaa4f 100644 --- a/engine.go +++ b/engine.go @@ -42,6 +42,13 @@ type Engine struct { TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database + + logSessionID bool // create session id +} + +// EnableSessionID if enable session id +func (engine *Engine) EnableSessionID(enable bool) { + engine.logSessionID = enable } // SetCacher sets cacher for the table diff --git a/log/logger_context.go b/log/logger_context.go index f80091f3..c2d94dc6 100644 --- a/log/logger_context.go +++ b/log/logger_context.go @@ -6,6 +6,7 @@ package log import ( "context" + "fmt" "time" ) @@ -18,6 +19,7 @@ type LogContext struct { Err error // SQL executed error } +// SQLLogger represents an interface to log SQL type SQLLogger interface { BeforeSQL(context LogContext) // only invoked when IsShowSQL is true AfterSQL(context LogContext) // only invoked when IsShowSQL is true @@ -43,55 +45,77 @@ var ( _ ContextLogger = &LoggerAdapter{} ) +// enumerate all the context keys +var ( + SessionIDKey = "__xorm_session_id" + SessionShowSQLKey = "__xorm_show_sql" +) + // LoggerAdapter wraps a Logger interafce as LoggerContext interface type LoggerAdapter struct { logger Logger } +// NewLoggerAdapter creates an adapter for old xorm logger interface func NewLoggerAdapter(logger Logger) ContextLogger { return &LoggerAdapter{ logger: logger, } } +// BeforeSQL implements ContextLogger func (l *LoggerAdapter) BeforeSQL(ctx LogContext) {} +// AfterSQL implements ContextLogger func (l *LoggerAdapter) AfterSQL(ctx LogContext) { + var sessionPart string + v := ctx.Ctx.Value(SessionIDKey) + if key, ok := v.(string); ok { + sessionPart = fmt.Sprintf(" [%s]", key) + } if ctx.ExecuteTime > 0 { - l.logger.Infof("[SQL] %v %v - %v", ctx.SQL, ctx.Args, ctx.ExecuteTime) + l.logger.Infof("[SQL]%s %s %v - %v", sessionPart, ctx.SQL, ctx.Args, ctx.ExecuteTime) } else { - l.logger.Infof("[SQL] %v %v", ctx.SQL, ctx.Args) + l.logger.Infof("[SQL]%s %s %v", sessionPart, ctx.SQL, ctx.Args) } } +// Debugf implements ContextLogger func (l *LoggerAdapter) Debugf(format string, v ...interface{}) { l.logger.Debugf(format, v...) } +// Errorf implements ContextLogger func (l *LoggerAdapter) Errorf(format string, v ...interface{}) { l.logger.Errorf(format, v...) } +// Infof implements ContextLogger func (l *LoggerAdapter) Infof(format string, v ...interface{}) { l.logger.Infof(format, v...) } +// Warnf implements ContextLogger func (l *LoggerAdapter) Warnf(format string, v ...interface{}) { l.logger.Warnf(format, v...) } +// Level implements ContextLogger func (l *LoggerAdapter) Level() LogLevel { return l.logger.Level() } +// SetLevel implements ContextLogger func (l *LoggerAdapter) SetLevel(lv LogLevel) { l.logger.SetLevel(lv) } +// ShowSQL implements ContextLogger func (l *LoggerAdapter) ShowSQL(show ...bool) { l.logger.ShowSQL(show...) } +// IsShowSQL implements ContextLogger func (l *LoggerAdapter) IsShowSQL() bool { return l.logger.IsShowSQL() } diff --git a/session.go b/session.go index 6b8bfbaf..13ebfe27 100644 --- a/session.go +++ b/session.go @@ -6,10 +6,14 @@ package xorm import ( "context" + "crypto/rand" + "crypto/sha256" "database/sql" + "encoding/hex" "errors" "fmt" "hash/crc32" + "io" "reflect" "strings" "time" @@ -19,6 +23,7 @@ import ( "xorm.io/xorm/core" "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/statements" + "xorm.io/xorm/log" "xorm.io/xorm/schemas" ) @@ -92,6 +97,17 @@ func (session *Session) Clone() *Session { return &sess } +func newSessionID() string { + hash := sha256.New() + _, err := io.CopyN(hash, rand.Reader, 50) + if err != nil { + return "????????????????????" + } + md := hash.Sum(nil) + mdStr := hex.EncodeToString(md) + return mdStr[0:20] +} + // Init reset the session as the init status. func (session *Session) Init() { session.statement = statements.NewStatement( @@ -119,7 +135,11 @@ func (session *Session) Init() { session.lastSQL = "" session.lastSQLArgs = []interface{}{} - session.ctx = session.engine.defaultContext + if session.engine.logSessionID { + session.ctx = context.WithValue(session.engine.defaultContext, log.SessionIDKey, newSessionID()) + } else { + session.ctx = session.engine.defaultContext + } } // Close release the connection from pool @@ -264,12 +284,12 @@ func (session *Session) Cascade(trueOrFalse ...bool) *Session { } // MustLogSQL means record SQL or not and don't follow engine's setting -func (session *Session) MustLogSQL(log ...bool) *Session { +func (session *Session) MustLogSQL(logs ...bool) *Session { var showSQL = true - if len(log) > 0 { - showSQL = log[0] + if len(logs) > 0 { + showSQL = logs[0] } - session.ctx = context.WithValue(session.ctx, "__xorm_show_sql", showSQL) + session.ctx = context.WithValue(session.ctx, log.SessionShowSQLKey, showSQL) return session } diff --git a/xorm.go b/xorm.go index e9cd7415..2fe32e50 100644 --- a/xorm.go +++ b/xorm.go @@ -51,6 +51,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { driverName: driverName, dataSourceName: dataSourceName, db: db, + logSessionID: false, } if dialect.URI().DBType == schemas.SQLITE { From 78bb4c711d555ee3c04909040e3680d16b33be93 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 26 Mar 2020 13:24:11 +0000 Subject: [PATCH 093/112] Improve codes (#1630) Improve codes Reviewed-on: https://gitea.com/xorm/xorm/pulls/1630 --- engine.go | 4 +- error.go | 2 + session.go | 119 +++++++++++++++++++++++----------------------- session_get.go | 2 +- session_insert.go | 22 ++++----- 5 files changed, 75 insertions(+), 74 deletions(-) diff --git a/engine.go b/engine.go index 80baaa4f..09fcd774 100644 --- a/engine.go +++ b/engine.go @@ -232,9 +232,7 @@ func (engine *Engine) Dialect() dialects.Dialect { // NewSession New a session func (engine *Engine) NewSession() *Session { - session := &Session{engine: engine} - session.Init() - return session + return newSession(engine) } // Close the engine diff --git a/error.go b/error.go index 3708ce4f..cfa5c819 100644 --- a/error.go +++ b/error.go @@ -9,6 +9,8 @@ import ( ) var ( + // ErrPtrSliceType represents a type error + ErrPtrSliceType = errors.New("A point to a slice is needed") // ErrParamsType params error ErrParamsType = errors.New("Params type error") // ErrTableNotFound table not found error diff --git a/session.go b/session.go index 13ebfe27..81afcc03 100644 --- a/session.go +++ b/session.go @@ -47,24 +47,24 @@ func (e ErrFieldIsNotValid) Error() string { return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName) } -type sessionType int +type sessionType bool const ( - engineSession sessionType = iota - groupSession + engineSession sessionType = false + groupSession sessionType = true ) // Session keep a pointer to sql.DB and provides all execution of all // kind of database operations. type Session struct { - db *core.DB engine *Engine tx *core.Tx statement *statements.Statement isAutoCommit bool isCommitedOrRollbacked bool isAutoClose bool - + isClosed bool + prepareStmt bool // Automatically reset the statement after operations that execute a SQL // query such as Count(), Find(), Get(), ... autoResetStatement bool @@ -75,28 +75,19 @@ type Session struct { afterDeleteBeans map[interface{}]*[]func(interface{}) // -- - beforeClosures []func(interface{}) - afterClosures []func(interface{}) - + beforeClosures []func(interface{}) + afterClosures []func(interface{}) afterProcessors []executedProcessor - prepareStmt bool - stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) + stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) lastSQL string lastSQLArgs []interface{} - showSQL bool ctx context.Context sessionType sessionType } -// Clone copy all the session's content and return a new session -func (session *Session) Clone() *Session { - var sess = *session - return &sess -} - func newSessionID() string { hash := sha256.New() _, err := io.CopyN(hash, rand.Reader, 50) @@ -108,63 +99,77 @@ func newSessionID() string { return mdStr[0:20] } -// Init reset the session as the init status. -func (session *Session) Init() { - session.statement = statements.NewStatement( - session.engine.dialect, - session.engine.tagParser, - session.engine.DatabaseTZ, - ) - session.db = session.engine.db - session.isAutoCommit = true - session.isCommitedOrRollbacked = false - session.isAutoClose = false - session.autoResetStatement = true - session.prepareStmt = false - - // !nashtsai! is lazy init better? - session.afterInsertBeans = make(map[interface{}]*[]func(interface{}), 0) - session.afterUpdateBeans = make(map[interface{}]*[]func(interface{}), 0) - session.afterDeleteBeans = make(map[interface{}]*[]func(interface{}), 0) - session.beforeClosures = make([]func(interface{}), 0) - session.afterClosures = make([]func(interface{}), 0) - session.stmtCache = make(map[uint32]*core.Stmt) - - session.afterProcessors = make([]executedProcessor, 0) - - session.lastSQL = "" - session.lastSQLArgs = []interface{}{} - - if session.engine.logSessionID { - session.ctx = context.WithValue(session.engine.defaultContext, log.SessionIDKey, newSessionID()) +func newSession(engine *Engine) *Session { + var ctx context.Context + if engine.logSessionID { + ctx = context.WithValue(engine.defaultContext, log.SessionIDKey, newSessionID()) } else { - session.ctx = session.engine.defaultContext + ctx = engine.defaultContext + } + + return &Session{ + ctx: ctx, + engine: engine, + tx: nil, + statement: statements.NewStatement( + engine.dialect, + engine.tagParser, + engine.DatabaseTZ, + ), + isClosed: false, + isAutoCommit: true, + isCommitedOrRollbacked: false, + isAutoClose: false, + 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), + beforeClosures: make([]func(interface{}), 0), + afterClosures: make([]func(interface{}), 0), + afterProcessors: make([]executedProcessor, 0), + stmtCache: make(map[uint32]*core.Stmt), + + lastSQL: "", + lastSQLArgs: make([]interface{}, 0), + + sessionType: engineSession, } } // Close release the connection from pool -func (session *Session) Close() { +func (session *Session) Close() error { for _, v := range session.stmtCache { - v.Close() + if err := v.Close(); err != nil { + return err + } } - if session.db != nil { + if !session.isClosed { // When Close be called, if session is a transaction and do not call // Commit or Rollback, then call Rollback. if session.tx != nil && !session.isCommitedOrRollbacked { - session.Rollback() + if err := session.Rollback(); err != nil { + return err + } } session.tx = nil session.stmtCache = nil - session.db = nil + session.isClosed = true } + return nil +} + +func (session *Session) db() *core.DB { + return session.engine.db } func (session *Session) getQueryer() core.Queryer { if session.tx != nil { return session.tx } - return session.db + return session.db() } // ContextCache enable context cache or not @@ -175,7 +180,7 @@ func (session *Session) ContextCache(context contexts.ContextCache) *Session { // IsClosed returns if session is closed func (session *Session) IsClosed() bool { - return session.db == nil + return session.isClosed } func (session *Session) resetStatement() { @@ -320,11 +325,7 @@ func (session *Session) Having(conditions string) *Session { // DB db return the wrapper of sql.DB func (session *Session) DB() *core.DB { - if session.db == nil { - session.db = session.engine.DB() - session.stmtCache = make(map[uint32]*core.Stmt, 0) - } - return session.db + return session.db() } func cleanupProcessorsClosures(slices *[]func(interface{})) { diff --git a/session_get.go b/session_get.go index e56ef2d7..afedcd1f 100644 --- a/session_get.go +++ b/session_get.go @@ -242,7 +242,7 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, if err != nil { return false, err } - // close it before covert data + // close it before convert data rows.Close() dataStruct := utils.ReflectValue(bean) diff --git a/session_insert.go b/session_insert.go index 3a0a7066..1270d5db 100644 --- a/session_insert.go +++ b/session_insert.go @@ -112,13 +112,14 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error return 0, ErrTableNotFound } - table := session.statement.RefTable - size := sliceValue.Len() - - var colNames []string - var colMultiPlaces []string - var args []interface{} - var cols []*schemas.Column + var ( + table = session.statement.RefTable + size = sliceValue.Len() + colNames []string + colMultiPlaces []string + args []interface{} + cols []*schemas.Column + ) for i := 0; i < size; i++ { v := sliceValue.Index(i) @@ -265,12 +266,11 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) if sliceValue.Kind() != reflect.Slice { - return 0, ErrParamsType - + return 0, ErrPtrSliceType } if sliceValue.Len() <= 0 { - return 0, nil + return 0, ErrNoElementsOnSlice } return session.innerInsertMulti(rowsSlicePtr) @@ -483,7 +483,7 @@ func (session *Session) cacheInsert(table string) error { if cacher == nil { return nil } - session.engine.logger.Debugf("[cache] clear sql: %v", table) + session.engine.logger.Debugf("[cache] clear SQL: %v", table) cacher.ClearIds(table) return nil } From 2ac051f07535be24618a7ba3f0f968e6092cf359 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 27 Mar 2020 03:13:25 +0000 Subject: [PATCH 094/112] Improve insert map generating SQL (#1634) Fix writeArg Improve insert map generating SQL Reviewed-on: https://gitea.com/xorm/xorm/pulls/1634 --- internal/statements/insert.go | 108 ++++++++++++++++++++------ internal/statements/statement_args.go | 32 +++----- session_insert.go | 70 +---------------- 3 files changed, 98 insertions(+), 112 deletions(-) diff --git a/internal/statements/insert.go b/internal/statements/insert.go index db2fc91c..6cbbbeda 100644 --- a/internal/statements/insert.go +++ b/internal/statements/insert.go @@ -5,6 +5,7 @@ package statements import ( + "fmt" "strings" "xorm.io/builder" @@ -23,18 +24,15 @@ func (statement *Statement) writeInsertOutput(buf *strings.Builder, table *schem return nil } +// GenInsertSQL generates insert beans SQL func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) (string, []interface{}, error) { var ( + buf = builder.NewWriter() + exprs = statement.ExprColumns table = statement.RefTable tableName = statement.TableName() - exprs = statement.ExprColumns - colPlaces = strings.Repeat("?, ", len(colNames)) ) - if exprs.Len() <= 0 && len(colPlaces) > 0 { - colPlaces = colPlaces[0 : len(colPlaces)-2] - } - var buf = builder.NewWriter() if _, err := buf.WriteString("INSERT INTO "); err != nil { return "", nil, err } @@ -43,7 +41,7 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } - if len(colPlaces) <= 0 { + if len(colNames) <= 0 { if statement.dialect.URI().DBType == schemas.MYSQL { if _, err := buf.WriteString(" VALUES ()"); err != nil { return "", nil, err @@ -65,13 +63,14 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } + if _, err := buf.WriteString(")"); err != nil { + return "", nil, err + } + if err := statement.writeInsertOutput(buf.Builder, table); err != nil { + return "", nil, err + } + if statement.Conds().IsValid() { - if _, err := buf.WriteString(")"); err != nil { - return "", nil, err - } - if err := statement.writeInsertOutput(buf.Builder, table); err != nil { - return "", nil, err - } if _, err := buf.WriteString(" SELECT "); err != nil { return "", nil, err } @@ -105,21 +104,20 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return "", nil, err } } else { - buf.Append(args...) - - if _, err := buf.WriteString(")"); err != nil { - return "", nil, err - } - if err := statement.writeInsertOutput(buf.Builder, table); err != nil { - return "", nil, err - } if _, err := buf.WriteString(" VALUES ("); err != nil { return "", nil, err } - if _, err := buf.WriteString(colPlaces); err != nil { + + if err := statement.WriteArgs(buf, args); err != nil { return "", nil, err } + if len(exprs.Args) > 0 { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + } + if err := exprs.WriteArgs(buf); err != nil { return "", nil, err } @@ -141,3 +139,69 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{}) return buf.String(), buf.Args(), nil } + +// GenInsertMapSQL generates insert map SQL +func (statement *Statement) GenInsertMapSQL(columns []string, args []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() { + if _, err := buf.WriteString(") SELECT "); err != nil { + return "", nil, err + } + + if err := statement.WriteArgs(buf, args); err != nil { + return "", nil, err + } + + if len(exprs.Args) > 0 { + if _, err := buf.WriteString(","); err != nil { + return "", nil, err + } + if err := exprs.WriteArgs(buf); err != nil { + return "", nil, err + } + } + + if _, err := buf.WriteString(fmt.Sprintf(" FROM %s WHERE ", statement.quote(tableName))); err != nil { + return "", nil, err + } + + if err := statement.Conds().WriteTo(buf); err != nil { + return "", nil, err + } + } else { + if _, err := buf.WriteString(") VALUES ("); err != nil { + return "", nil, err + } + if err := statement.WriteArgs(buf, args); err != nil { + return "", nil, err + } + + if len(exprs.Args) > 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 + } + } + + return buf.String(), buf.Args(), nil +} diff --git a/internal/statements/statement_args.go b/internal/statements/statement_args.go index 7d1ef9eb..dc14467d 100644 --- a/internal/statements/statement_args.go +++ b/internal/statements/statement_args.go @@ -79,28 +79,6 @@ const insertSelectPlaceHolder = true func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { - case bool: - if statement.dialect.URI().DBType == schemas.MSSQL { - if argv { - if _, err := w.WriteString("1"); err != nil { - return err - } - } else { - if _, err := w.WriteString("0"); err != nil { - return err - } - } - } else { - if argv { - if _, err := w.WriteString("true"); err != nil { - return err - } - } else { - if _, err := w.WriteString("false"); err != nil { - return err - } - } - } case *builder.Builder: if _, err := w.WriteString("("); err != nil { return err @@ -116,7 +94,15 @@ func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) er if err := w.WriteByte('?'); err != nil { return err } - w.Append(arg) + if v, ok := arg.(bool); ok && statement.dialect.URI().DBType == schemas.MSSQL { + if v { + w.Append(1) + } else { + w.Append(0) + } + } else { + w.Append(arg) + } } else { var convertFunc = convertStringSingleQuote if statement.dialect.URI().DBType == schemas.MYSQL { diff --git a/session_insert.go b/session_insert.go index 1270d5db..2c46a59b 100644 --- a/session_insert.go +++ b/session_insert.go @@ -12,7 +12,6 @@ import ( "strconv" "strings" - "xorm.io/builder" "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -623,74 +622,11 @@ func (session *Session) insertMap(columns []string, args []interface{}) (int64, return 0, ErrTableNotFound } - exprs := session.statement.ExprColumns - w := builder.NewWriter() - // if insert where - if session.statement.Conds().IsValid() { - if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { - return 0, err - } - - if err := session.engine.dialect.Quoter().JoinWrite(w.Builder, append(columns, exprs.ColNames...), ","); err != nil { - return 0, err - } - - if _, err := w.WriteString(") SELECT "); err != nil { - return 0, err - } - - if err := session.statement.WriteArgs(w, args); err != nil { - return 0, err - } - - if len(exprs.Args) > 0 { - if _, err := w.WriteString(","); err != nil { - return 0, err - } - if err := exprs.WriteArgs(w); err != nil { - return 0, err - } - } - - if _, err := w.WriteString(fmt.Sprintf(" FROM %s WHERE ", session.engine.Quote(tableName))); err != nil { - return 0, err - } - - if err := session.statement.Conds().WriteTo(w); err != nil { - return 0, err - } - } else { - qm := strings.Repeat("?,", len(columns)) - qm = qm[:len(qm)-1] - - if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { - return 0, err - } - - if err := session.engine.dialect.Quoter().JoinWrite(w.Builder, append(columns, exprs.ColNames...), ","); err != nil { - return 0, err - } - if _, err := w.WriteString(fmt.Sprintf(") VALUES (%s", qm)); err != nil { - return 0, err - } - - w.Append(args...) - if len(exprs.Args) > 0 { - if _, err := w.WriteString(","); err != nil { - return 0, err - } - if err := exprs.WriteArgs(w); err != nil { - return 0, err - } - } - if _, err := w.WriteString(")"); err != nil { - return 0, err - } + sql, args, err := session.statement.GenInsertMapSQL(columns, args) + if err != nil { + return 0, err } - sql := w.String() - args = w.Args() - if err := session.cacheInsert(tableName); err != nil { return 0, err } From 79bdda3cf182250d30e94332ed45fab1d9b7ac01 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 27 Mar 2020 07:13:04 +0000 Subject: [PATCH 095/112] Move all integrations tests to a standalone sub package (#1635) Fix vet Remove unused files Move all integrations tests to a standalone sub package Reviewed-on: https://gitea.com/xorm/xorm/pulls/1635 --- .drone.yml | 9 +- .gitignore | 2 + Makefile | 50 ++++++----- README.md | 2 + convert.go | 50 ----------- dialects/mssql.go | 6 +- dialects/mysql.go | 6 +- dialects/oracle.go | 6 +- dialects/postgres.go | 6 +- dialects/sqlite3.go | 6 +- doc.go | 2 +- engine.go | 54 ++++++++++++ cache_test.go => integrations/cache_test.go | 8 +- .../engine_group_test.go | 12 +-- engine_test.go => integrations/engine_test.go | 44 +++++++--- integrations/main_test.go | 13 +++ .../processors_test.go | 24 +++--- rows_test.go => integrations/rows_test.go | 8 +- .../session_cols_test.go | 8 +- .../session_cond_test.go | 8 +- .../session_delete_test.go | 10 +-- .../session_exist_test.go | 8 +- .../session_find_test.go | 79 ++++++++---------- .../session_get_test.go | 82 +++++++++++++++---- .../session_insert_test.go | 54 ++++++------ .../session_iterate_test.go | 6 +- .../session_pk_test.go | 34 ++++---- .../session_query_test.go | 20 ++--- .../session_raw_test.go | 4 +- .../session_schema_test.go | 32 ++++---- .../session_stats_test.go | 18 ++-- .../session_test.go | 8 +- .../session_tx_test.go | 10 +-- .../session_update_test.go | 59 ++++++------- tags_test.go => integrations/tags_test.go | 64 +++++++-------- xorm_test.go => integrations/tests.go | 28 ++----- time_test.go => integrations/time_test.go | 24 +++--- .../types_null_test.go | 20 ++--- types_test.go => integrations/types_test.go | 15 ++-- xorm.go | 82 ------------------- 40 files changed, 503 insertions(+), 478 deletions(-) rename cache_test.go => integrations/cache_test.go (97%) rename engine_group_test.go => integrations/engine_group_test.go (77%) rename engine_test.go => integrations/engine_test.go (71%) create mode 100644 integrations/main_test.go rename processors_test.go => integrations/processors_test.go (97%) rename rows_test.go => integrations/rows_test.go (96%) rename session_cols_test.go => integrations/session_cols_test.go (96%) rename session_cond_test.go => integrations/session_cond_test.go (97%) rename session_delete_test.go => integrations/session_delete_test.go (97%) rename session_exist_test.go => integrations/session_exist_test.go (97%) rename session_find_test.go => integrations/session_find_test.go (93%) rename session_get_test.go => integrations/session_get_test.go (91%) rename session_insert_test.go => integrations/session_insert_test.go (96%) rename session_iterate_test.go => integrations/session_iterate_test.go (96%) rename session_pk_test.go => integrations/session_pk_test.go (96%) rename session_query_test.go => integrations/session_query_test.go (96%) rename session_raw_test.go => integrations/session_raw_test.go (94%) rename session_schema_test.go => integrations/session_schema_test.go (93%) rename session_stats_test.go => integrations/session_stats_test.go (95%) rename session_test.go => integrations/session_test.go (89%) rename session_tx_test.go => integrations/session_tx_test.go (96%) rename session_update_test.go => integrations/session_update_test.go (96%) rename tags_test.go => integrations/tags_test.go (96%) rename xorm_test.go => integrations/tests.go (89%) rename time_test.go => integrations/time_test.go (97%) rename types_null_test.go => integrations/types_null_test.go (94%) rename types_test.go => integrations/types_test.go (96%) delete mode 100644 xorm.go diff --git a/.drone.yml b/.drone.yml index 0863cce2..7a18e0d6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,12 +3,13 @@ kind: pipeline name: testing steps: - name: test-vet - image: golang:1.11 + image: golang:1.11 # The lowest golang requirement environment: GO111MODULE: "on" GOPROXY: "https://goproxy.cn" commands: - - go vet + - make vet + - make test when: event: - push @@ -23,10 +24,6 @@ steps: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite - TEST_QUOTE_POLICY=reserved make test-sqlite - - go test ./caches/... ./contexts/... ./convert/... ./core/... ./dialects/... \ - ./log/... ./migrate/... ./names/... ./schemas/... ./tags/... \ - ./internal/json/... ./internal/statements/... ./internal/utils/... \ - when: event: - push diff --git a/.gitignore b/.gitignore index 0d321a6b..617d5da7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ test.db.sql .idea/ *coverage.out +test.db +integrations/*.sql diff --git a/Makefile b/Makefile index 88364f54..4cccacd8 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ TAGS ?= SED_INPLACE := sed -i GOFILES := $(shell find . -name "*.go" -type f) - -PACKAGES ?= $(shell GO111MODULE=on $(GO) list ./...) +INTEGRATION_PACKAGES := xorm.io/xorm/integrations +PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...)) TEST_COCKROACH_HOST ?= cockroach:26257 TEST_COCKROACH_SCHEMA ?= @@ -46,12 +46,12 @@ all: build .PHONY: build build: go-check $(GO_SOURCES) - $(GO) build + $(GO) build $(PACKAGES) .PHONY: clean clean: $(GO) clean -i ./... - rm -rf *.sql *.log test.db *coverage.out coverage.all + rm -rf *.sql *.log test.db *coverage.out coverage.all integrations/*.sql .PHONY: coverage coverage: @@ -92,7 +92,12 @@ help: @echo " - lint run code linter revive" @echo " - misspell check if a word is written wrong" @echo " - test run default unit test" - @echo " - test-sqlite run unit test for sqlite" + @echo " - test-cockroach run integration tests for cockroach" + @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-tidb run integration tests for tidb" @echo " - vet examines Go source code and reports suspicious constructs" .PHONY: lint @@ -120,95 +125,96 @@ misspell-check: misspell -error -i unknwon,destory $(GOFILES) .PHONY: test -test: test-sqlite +test: go-check + $(GO) test $(PACKAGES) .PNONY: test-cockroach test-cockroach: go-check - $(GO) test -race -db=postgres -schema='$(TEST_COCKROACH_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + $(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 .PHONY: test-cockroach\#% test-cockroach\#%: go-check - $(GO) test -race -run $* -db=postgres -schema='$(TEST_COCKROACH_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -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 .PNONY: test-mssql test-mssql: go-check - $(GO) test -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mssql\#% test-mssql\#%: go-check - $(GO) test -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mymysql test-mymysql: go-check - $(GO) test -v -race -db=mymysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + $(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 .PNONY: test-mymysql\#% test-mymysql\#%: go-check - $(GO) test -v -race -run $* -db=mymysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -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 .PNONY: test-mysql test-mysql: go-check - $(GO) test -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + $(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 .PHONY: test-mysql\#% test-mysql\#%: go-check - $(GO) test -v -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ + $(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)" \ -coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-postgres test-postgres: go-check - $(GO) test -v -race -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + $(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 .PHONY: test-postgres\#% test-postgres\#%: go-check - $(GO) test -v -race -run $* -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -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 .PHONY: test-sqlite test-sqlite: go-check - $(GO) test -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=sqlite3 -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 -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=sqlite3 -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 -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=sqlite3 -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 -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ + $(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 .PHONY: test-tidb\#% test-tidb\#%: go-check - $(GO) test -v -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -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 .PHONY: vet vet: - $(GO) vet $(PACKAGES) \ No newline at end of file + $(GO) vet $(shell $(GO) list ./...) \ No newline at end of file diff --git a/README.md b/README.md index 2dc4d6eb..7334e6d4 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ Drivers for Go's sql package which currently support database/sql includes: * Create Engine +Firstly, we should new an engine for a database. + ```Go engine, err := xorm.NewEngine(driverName, dataSourceName) ``` diff --git a/convert.go b/convert.go index 90a9059e..c19d30e0 100644 --- a/convert.go +++ b/convert.go @@ -284,56 +284,6 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) } -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 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 asBool(bs []byte) (bool, error) { if len(bs) == 0 { return false, nil diff --git a/dialects/mssql.go b/dialects/mssql.go index ae3f4afd..8ef924b8 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -205,7 +205,11 @@ var ( "PROC": true, } - mssqlQuoter = schemas.Quoter{'[', ']', schemas.AlwaysReserve} + mssqlQuoter = schemas.Quoter{ + Prefix: '[', + Suffix: ']', + IsReserved: schemas.AlwaysReserve, + } ) type mssql struct { diff --git a/dialects/mysql.go b/dialects/mysql.go index b61bc7da..ac916c89 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -162,7 +162,11 @@ var ( "ZEROFILL": true, } - mysqlQuoter = schemas.Quoter{'`', '`', schemas.AlwaysReserve} + mysqlQuoter = schemas.Quoter{ + Prefix: '`', + Suffix: '`', + IsReserved: schemas.AlwaysReserve, + } ) type mysql struct { diff --git a/dialects/oracle.go b/dialects/oracle.go index c8aaf97e..2620b0bb 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -499,7 +499,11 @@ var ( "ZONE": true, } - oracleQuoter = schemas.Quoter{'[', ']', schemas.AlwaysReserve} + oracleQuoter = schemas.Quoter{ + Prefix: '[', + Suffix: ']', + IsReserved: schemas.AlwaysReserve, + } ) type oracle struct { diff --git a/dialects/postgres.go b/dialects/postgres.go index ac64c093..1996c49d 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -767,7 +767,11 @@ var ( "ZONE": true, } - postgresQuoter = schemas.Quoter{'"', '"', schemas.AlwaysReserve} + postgresQuoter = schemas.Quoter{ + Prefix: '"', + Suffix: '"', + IsReserved: schemas.AlwaysReserve, + } ) var ( diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 812a3698..0e910934 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -144,7 +144,11 @@ var ( "WITHOUT": true, } - sqlite3Quoter = schemas.Quoter{'`', '`', schemas.AlwaysReserve} + sqlite3Quoter = schemas.Quoter{ + Prefix: '`', + Suffix: '`', + IsReserved: schemas.AlwaysReserve, + } ) type sqlite3 struct { diff --git a/doc.go b/doc.go index 8df4fb30..ea6a2226 100644 --- a/doc.go +++ b/doc.go @@ -8,7 +8,7 @@ Package xorm is a simple and powerful ORM for Go. Installation -Make sure you have installed Go 1.6+ and then: +Make sure you have installed Go 1.11+ and then: go get xorm.io/xorm diff --git a/engine.go b/engine.go index 09fcd774..d99e15db 100644 --- a/engine.go +++ b/engine.go @@ -12,6 +12,7 @@ import ( "io" "os" "reflect" + "runtime" "strconv" "strings" "time" @@ -46,6 +47,59 @@ type Engine struct { logSessionID bool // create session id } +// NewEngine new a db manager according to the parameter. Currently support four +// drivers +func NewEngine(driverName string, dataSourceName string) (*Engine, error) { + dialect, err := dialects.OpenDialect(driverName, dataSourceName) + if err != nil { + return nil, err + } + + db, err := core.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + + cacherMgr := caches.NewManager() + mapper := names.NewCacheMapper(new(names.SnakeMapper)) + tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) + + engine := &Engine{ + dialect: dialect, + TZLocation: time.Local, + defaultContext: context.Background(), + cacherMgr: cacherMgr, + tagParser: tagParser, + driverName: driverName, + dataSourceName: dataSourceName, + db: db, + logSessionID: false, + } + + if dialect.URI().DBType == schemas.SQLITE { + engine.DatabaseTZ = time.UTC + } else { + engine.DatabaseTZ = time.Local + } + + logger := log.NewSimpleLogger(os.Stdout) + logger.SetLevel(log.LOG_INFO) + engine.SetLogger(log.NewLoggerAdapter(logger)) + + runtime.SetFinalizer(engine, func(engine *Engine) { + engine.Close() + }) + + return engine, nil +} + +// NewEngineWithParams new a db manager with params. The params will be passed to dialects. +func NewEngineWithParams(driverName string, dataSourceName string, params map[string]string) (*Engine, error) { + engine, err := NewEngine(driverName, dataSourceName) + engine.dialect.SetParams(params) + return engine, err +} + // EnableSessionID if enable session id func (engine *Engine) EnableSessionID(enable bool) { engine.logSessionID = enable diff --git a/cache_test.go b/integrations/cache_test.go similarity index 97% rename from cache_test.go rename to integrations/cache_test.go index 1606d5bc..44e817b1 100644 --- a/cache_test.go +++ b/integrations/cache_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" @@ -14,7 +14,7 @@ import ( ) func TestCacheFind(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type MailBox struct { Id int64 `xorm:"pk"` @@ -89,7 +89,7 @@ func TestCacheFind(t *testing.T) { } func TestCacheFind2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type MailBox2 struct { Id uint64 `xorm:"pk"` @@ -140,7 +140,7 @@ func TestCacheFind2(t *testing.T) { } func TestCacheGet(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type MailBox3 struct { Id uint64 diff --git a/engine_group_test.go b/integrations/engine_group_test.go similarity index 77% rename from engine_group_test.go rename to integrations/engine_group_test.go index 76aad6d2..635f73a6 100644 --- a/engine_group_test.go +++ b/integrations/engine_group_test.go @@ -2,26 +2,28 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" - "github.com/stretchr/testify/assert" + "xorm.io/xorm" "xorm.io/xorm/log" "xorm.io/xorm/schemas" + + "github.com/stretchr/testify/assert" ) func TestEngineGroup(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) - master := testEngine.(*Engine) + master := testEngine.(*xorm.Engine) if master.Dialect().URI().DBType == schemas.SQLITE { t.Skip() return } - eg, err := NewEngineGroup(master, []*Engine{master}) + eg, err := xorm.NewEngineGroup(master, []*xorm.Engine{master}) assert.NoError(t, err) eg.SetMaxIdleConns(10) diff --git a/engine_test.go b/integrations/engine_test.go similarity index 71% rename from engine_test.go rename to integrations/engine_test.go index b691d909..19c5285d 100644 --- a/engine_test.go +++ b/integrations/engine_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "context" @@ -11,25 +11,38 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "xorm.io/xorm" "xorm.io/xorm/schemas" + + _ "github.com/denisenkom/go-mssqldb" + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" + _ "github.com/ziutek/mymysql/godrv" ) +func TestPing(t *testing.T) { + if err := testEngine.Ping(); err != nil { + t.Fatal(err) + } +} + func TestPingContext(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) ctx, canceled := context.WithTimeout(context.Background(), time.Nanosecond) defer canceled() time.Sleep(time.Nanosecond) - err := testEngine.(*Engine).PingContext(ctx) + err := testEngine.(*xorm.Engine).PingContext(ctx) assert.Error(t, err) assert.Contains(t, err.Error(), "context deadline exceeded") } func TestAutoTransaction(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestTx struct { Id int64 `xorm:"autoincr pk"` @@ -39,10 +52,10 @@ func TestAutoTransaction(t *testing.T) { assert.NoError(t, testEngine.Sync2(new(TestTx))) - engine := testEngine.(*Engine) + engine := testEngine.(*xorm.Engine) // will success - engine.Transaction(func(session *Session) (interface{}, error) { + engine.Transaction(func(session *xorm.Session) (interface{}, error) { _, err := session.Insert(TestTx{Msg: "hi"}) assert.NoError(t, err) @@ -54,7 +67,7 @@ func TestAutoTransaction(t *testing.T) { assert.EqualValues(t, true, has) // will rollback - _, err = engine.Transaction(func(session *Session) (interface{}, error) { + _, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) { _, err := session.Insert(TestTx{Msg: "hello"}) assert.NoError(t, err) @@ -67,8 +80,17 @@ func TestAutoTransaction(t *testing.T) { assert.EqualValues(t, false, has) } +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)) + }) + } +} + func TestDump(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestDumpStruct struct { Id int64 @@ -89,7 +111,7 @@ func TestDump(t *testing.T) { os.Remove(fp) assert.NoError(t, testEngine.DumpAllToFile(fp)) - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) sess := testEngine.NewSession() defer sess.Close() @@ -107,7 +129,7 @@ func TestDump(t *testing.T) { } func TestSetSchema(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) if testEngine.Dialect().URI().DBType == schemas.POSTGRES { oldSchema := testEngine.Dialect().URI().Schema diff --git a/integrations/main_test.go b/integrations/main_test.go new file mode 100644 index 00000000..225ae45a --- /dev/null +++ b/integrations/main_test.go @@ -0,0 +1,13 @@ +// 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 integrations + +import ( + "testing" +) + +func TestMain(m *testing.M) { + MainTest(m) +} diff --git a/processors_test.go b/integrations/processors_test.go similarity index 97% rename from processors_test.go rename to integrations/processors_test.go index 21d4a7fb..e349988d 100644 --- a/processors_test.go +++ b/integrations/processors_test.go @@ -2,18 +2,20 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "errors" "fmt" "testing" + "xorm.io/xorm" + "github.com/stretchr/testify/assert" ) func TestBefore_Get(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type BeforeTable struct { Id int64 @@ -40,7 +42,7 @@ func TestBefore_Get(t *testing.T) { } func TestBefore_Find(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type BeforeTable2 struct { Id int64 @@ -101,7 +103,7 @@ func (p *ProcessorsStruct) BeforeDelete() { p.B4DeleteFlag = 1 } -func (p *ProcessorsStruct) BeforeSet(col string, cell Cell) { +func (p *ProcessorsStruct) BeforeSet(col string, cell xorm.Cell) { p.BeforeSetFlag = p.BeforeSetFlag + 1 } @@ -117,12 +119,12 @@ func (p *ProcessorsStruct) AfterDelete() { p.AfterDeletedFlag = 1 } -func (p *ProcessorsStruct) AfterSet(col string, cell Cell) { +func (p *ProcessorsStruct) AfterSet(col string, cell xorm.Cell) { p.AfterSetFlag = p.AfterSetFlag + 1 } func TestProcessors(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&ProcessorsStruct{}) assert.NoError(t, err) @@ -356,7 +358,7 @@ func TestProcessors(t *testing.T) { } func TestProcessorsTx(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&ProcessorsStruct{}) assert.NoError(t, err) @@ -830,19 +832,19 @@ type AfterLoadStructB struct { Err error `xorm:"-"` } -func (s *AfterLoadStructB) AfterLoad(session *Session) { +func (s *AfterLoadStructB) AfterLoad(session *xorm.Session) { has, err := session.ID(s.AId).NoAutoCondition().Get(&s.A) if err != nil { s.Err = err return } if !has { - s.Err = ErrNotExist + s.Err = xorm.ErrNotExist } } func TestAfterLoadProcessor(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(AfterLoadStructA), new(AfterLoadStructB)) @@ -893,7 +895,7 @@ func (a *AfterInsertStruct) AfterInsert() { } func TestAfterInsert(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(AfterInsertStruct)) diff --git a/rows_test.go b/integrations/rows_test.go similarity index 96% rename from rows_test.go rename to integrations/rows_test.go index 439d3793..f68030a4 100644 --- a/rows_test.go +++ b/integrations/rows_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" @@ -11,7 +11,7 @@ import ( ) func TestRows(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserRows struct { Id int64 @@ -85,7 +85,7 @@ func TestRows(t *testing.T) { } func TestRowsMyTableName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserRowsMyTable struct { Id int64 @@ -140,7 +140,7 @@ func (UserRowsSpecTable) TableName() string { } func TestRowsSpecTableName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(UserRowsSpecTable))) cnt, err := testEngine.Insert(&UserRowsSpecTable{ diff --git a/session_cols_test.go b/integrations/session_cols_test.go similarity index 96% rename from session_cols_test.go rename to integrations/session_cols_test.go index 2847cf35..b74c6f8a 100644 --- a/session_cols_test.go +++ b/integrations/session_cols_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" @@ -13,7 +13,7 @@ import ( ) func TestSetExpr(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserExprIssue struct { Id int64 @@ -64,7 +64,7 @@ func TestSetExpr(t *testing.T) { } func TestCols(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type ColsTable struct { Id int64 @@ -96,7 +96,7 @@ func TestCols(t *testing.T) { } func TestMustCol(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type CustomerUpdate struct { Id int64 `form:"id" json:"id"` diff --git a/session_cond_test.go b/integrations/session_cond_test.go similarity index 97% rename from session_cond_test.go rename to integrations/session_cond_test.go index 8fb7afc3..a0a91cad 100644 --- a/session_cond_test.go +++ b/integrations/session_cond_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "errors" @@ -14,7 +14,7 @@ import ( ) func TestBuilder(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) const ( OpEqual int = iota @@ -102,7 +102,7 @@ func TestBuilder(t *testing.T) { } func TestIn(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(Userinfo))) cnt, err := testEngine.Insert([]Userinfo{ @@ -195,7 +195,7 @@ func TestIn(t *testing.T) { } func TestFindAndCount(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type FindAndCount struct { Id int64 diff --git a/session_delete_test.go b/integrations/session_delete_test.go similarity index 97% rename from session_delete_test.go rename to integrations/session_delete_test.go index 6fba860b..f3565963 100644 --- a/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" @@ -15,7 +15,7 @@ import ( ) func TestDelete(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserinfoDelete struct { Uid int64 `xorm:"id pk not null autoincr"` @@ -71,7 +71,7 @@ func TestDelete(t *testing.T) { } func TestDeleted(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type Deleted struct { Id int64 `xorm:"pk"` @@ -158,7 +158,7 @@ func TestDeleted(t *testing.T) { } func TestCacheDelete(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) oldCacher := testEngine.GetDefaultCacher() cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 1000) @@ -190,7 +190,7 @@ func TestCacheDelete(t *testing.T) { } func TestUnscopeDelete(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UnscopeDeleteStruct struct { Id int64 diff --git a/session_exist_test.go b/integrations/session_exist_test.go similarity index 97% rename from session_exist_test.go rename to integrations/session_exist_test.go index 0861382a..6247c91a 100644 --- a/session_exist_test.go +++ b/integrations/session_exist_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "context" @@ -13,7 +13,7 @@ import ( ) func TestExistStruct(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type RecordExist struct { Id int64 @@ -78,7 +78,7 @@ func TestExistStruct(t *testing.T) { } func TestExistStructForJoin(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type Number struct { Id int64 @@ -190,7 +190,7 @@ func TestExistContext(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(ContextQueryStruct)) _, err := testEngine.Insert(&ContextQueryStruct{Name: "1"}) diff --git a/session_find_test.go b/integrations/session_find_test.go similarity index 93% rename from session_find_test.go rename to integrations/session_find_test.go index 17364e67..00477235 100644 --- a/session_find_test.go +++ b/integrations/session_find_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" @@ -15,7 +15,7 @@ import ( ) func TestJoinLimit(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type Salary struct { Id int64 @@ -62,17 +62,8 @@ func TestJoinLimit(t *testing.T) { assert.NoError(t, err) } -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)) - }) - } -} - func TestWhere(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) @@ -85,7 +76,7 @@ func TestWhere(t *testing.T) { } func TestFind(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) @@ -100,7 +91,7 @@ func TestFind(t *testing.T) { } func TestFind2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) users := make([]*Userinfo, 0) assertSync(t, new(Userinfo)) @@ -125,7 +116,7 @@ func (TeamUser) TableName() string { func TestFind3(t *testing.T) { var teamUser = new(TeamUser) - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.Sync2(new(Team), teamUser) assert.NoError(t, err) @@ -179,7 +170,7 @@ func TestFind3(t *testing.T) { } func TestFindMap(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) cnt, err := testEngine.Insert(&Userinfo{ @@ -208,7 +199,7 @@ func TestFindMap(t *testing.T) { } func TestFindMap2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) users := make(map[int64]*Userinfo) @@ -217,7 +208,7 @@ func TestFindMap2(t *testing.T) { } func TestDistinct(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) _, err := testEngine.Insert(&Userinfo{ @@ -242,7 +233,7 @@ func TestDistinct(t *testing.T) { } func TestOrder(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) @@ -255,7 +246,7 @@ func TestOrder(t *testing.T) { } func TestGroupBy(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) @@ -264,7 +255,7 @@ func TestGroupBy(t *testing.T) { } func TestHaving(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) @@ -273,7 +264,7 @@ func TestHaving(t *testing.T) { } func TestOrderSameMapper(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() @@ -296,7 +287,7 @@ func TestOrderSameMapper(t *testing.T) { } func TestHavingSameMapper(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() @@ -313,7 +304,7 @@ func TestHavingSameMapper(t *testing.T) { } func TestFindInts(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") @@ -340,7 +331,7 @@ func TestFindInts(t *testing.T) { } func TestFindStrings(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") username := testEngine.GetColumnMapper().Obj2Table("Username") @@ -350,7 +341,7 @@ func TestFindStrings(t *testing.T) { } func TestFindMyString(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") username := testEngine.GetColumnMapper().Obj2Table("Username") @@ -361,7 +352,7 @@ func TestFindMyString(t *testing.T) { } func TestFindInterface(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") @@ -372,7 +363,7 @@ func TestFindInterface(t *testing.T) { } func TestFindSliceBytes(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") @@ -382,7 +373,7 @@ func TestFindSliceBytes(t *testing.T) { } func TestFindSlicePtrString(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") @@ -392,7 +383,7 @@ func TestFindSlicePtrString(t *testing.T) { } func TestFindMapBytes(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") @@ -402,7 +393,7 @@ func TestFindMapBytes(t *testing.T) { } func TestFindMapPtrString(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) userinfo := testEngine.GetTableMapper().Obj2Table("Userinfo") @@ -417,7 +408,7 @@ func TestFindBit(t *testing.T) { Msg bool `xorm:"bit"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(FindBitStruct)) cnt, err := testEngine.Insert([]FindBitStruct{ @@ -445,7 +436,7 @@ func TestFindMark(t *testing.T) { MarkA string `xorm:"VARCHAR(1)"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Mark)) cnt, err := testEngine.Insert([]Mark{ @@ -476,7 +467,7 @@ func TestFindAndCountOneFunc(t *testing.T) { Msg bool `xorm:"bit"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(FindAndCountStruct)) cnt, err := testEngine.Insert([]FindAndCountStruct{ @@ -544,7 +535,7 @@ func TestFindAndCountOneFuncWithDeleted(t *testing.T) { DeletedAt int64 `xorm:"deleted notnull default(0) index"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(CommentWithDeleted)) var comments []CommentWithDeleted @@ -569,7 +560,7 @@ func TestFindAndCount2(t *testing.T) { CreateBy *TestFindAndCountUser `xorm:"'create_by'"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestFindAndCountUser), new(TestFindAndCountHotel)) var u = TestFindAndCountUser{ @@ -627,7 +618,7 @@ func (device *FindMapDevice) TableName() string { } func TestFindMapStringId(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(FindMapDevice)) cnt, err := testEngine.Insert(&FindMapDevice{ @@ -698,7 +689,7 @@ func TestFindExtends(t *testing.T) { FindExtendsB `xorm:"extends"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(FindExtendsA)) cnt, err := testEngine.Insert(&FindExtendsA{ @@ -733,7 +724,7 @@ func TestFindExtends3(t *testing.T) { FindExtendsBB `xorm:"extends"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(FindExtendsAA)) cnt, err := testEngine.Insert(&FindExtendsAA{ @@ -769,7 +760,7 @@ func TestFindCacheLimit(t *testing.T) { Created time.Time `xorm:"created"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(InviteCode)) cnt, err := testEngine.Insert(&InviteCode{ @@ -814,7 +805,7 @@ func TestFindJoin(t *testing.T) { Id int64 } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(SceneItem), new(DeviceUserPrivrels), new(Order)) var scenes []SceneItem @@ -844,7 +835,7 @@ func TestJoinFindLimit(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(JoinFindLimit1), new(JoinFindLimit2)) var finds []JoinFindLimit1 @@ -876,7 +867,7 @@ func TestMoreExtends(t *testing.T) { Users MoreExtendsUsers `xorm:"extends" json:"users"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(MoreExtendsUsers), new(MoreExtendsBooks)) var books []MoreExtendsBooksExtend @@ -904,7 +895,7 @@ func TestDistinctAndCols(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(DistinctAndCols)) cnt, err := testEngine.Insert(&DistinctAndCols{ diff --git a/session_get_test.go b/integrations/session_get_test.go similarity index 91% rename from session_get_test.go rename to integrations/session_get_test.go index b83a118b..4e50f9ab 100644 --- a/session_get_test.go +++ b/integrations/session_get_test.go @@ -2,21 +2,73 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "database/sql" "fmt" + "strconv" "testing" "time" - "github.com/stretchr/testify/assert" "xorm.io/xorm/contexts" "xorm.io/xorm/schemas" + + "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()) + assert.NoError(t, PrepareEngine()) type GetVar struct { Id int64 `xorm:"autoincr pk"` @@ -221,7 +273,7 @@ func TestGetVar(t *testing.T) { } func TestGetStruct(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserinfoGet struct { Uid int `xorm:"pk autoincr"` @@ -276,7 +328,7 @@ func TestGetStruct(t *testing.T) { } func TestGetSlice(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserinfoSlice struct { Uid int `xorm:"pk autoincr"` @@ -292,7 +344,7 @@ func TestGetSlice(t *testing.T) { } func TestGetError(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetError struct { Uid int `xorm:"pk autoincr"` @@ -312,7 +364,7 @@ func TestGetError(t *testing.T) { } func TestJSONString(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type JsonString struct { Id int64 @@ -345,7 +397,7 @@ func TestJSONString(t *testing.T) { } func TestGetActionMapping(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type ActionMapping struct { ActionId string `xorm:"pk"` @@ -382,7 +434,7 @@ func TestGetStructId(t *testing.T) { Id int64 } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestGetStruct)) _, err := testEngine.Insert(&TestGetStruct{}) @@ -409,7 +461,7 @@ func TestContextGet(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(ContextGetStruct)) _, err := testEngine.Insert(&ContextGetStruct{Name: "1"}) @@ -447,7 +499,7 @@ func TestContextGet2(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(ContextGetStruct2)) _, err := testEngine.Insert(&ContextGetStruct2{Name: "1"}) @@ -486,7 +538,7 @@ func (MyGetCustomTableImpletation) TableName() string { } func TestGetCustomTableInterface(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Table(getCustomTableName).Sync2(new(MyGetCustomTableImpletation))) exist, err := testEngine.IsTableExist(getCustomTableName) @@ -511,7 +563,7 @@ func TestGetNullVar(t *testing.T) { Age int } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestGetNullVarStruct)) affected, err := testEngine.Exec("insert into " + testEngine.TableName(new(TestGetNullVarStruct), true) + " (name,age) values (null,null)") @@ -596,7 +648,7 @@ func TestCustomTypes(t *testing.T) { Age MyInt } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestCustomizeStruct)) var s = TestCustomizeStruct{ @@ -627,7 +679,7 @@ func TestGetViaMapCond(t *testing.T) { Index int } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(GetViaMapCond)) var ( diff --git a/session_insert_test.go b/integrations/session_insert_test.go similarity index 96% rename from session_insert_test.go rename to integrations/session_insert_test.go index 5fded623..47789b8a 100644 --- a/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -10,11 +10,13 @@ import ( "testing" "time" + "xorm.io/xorm" + "github.com/stretchr/testify/assert" ) func TestInsertOne(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type Test struct { Id int64 `xorm:"autoincr pk"` @@ -31,7 +33,7 @@ func TestInsertOne(t *testing.T) { func TestInsertMulti(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestMulti struct { Id int64 `xorm:"int(11) pk"` Name string `xorm:"varchar(255)"` @@ -106,7 +108,7 @@ func callbackLooper(datas interface{}, step int, actionFunc func(interface{}) er } func TestInsertOneIfPkIsPoint(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestPoint struct { Id *int64 `xorm:"autoincr pk notnull 'id'"` @@ -122,7 +124,7 @@ func TestInsertOneIfPkIsPoint(t *testing.T) { } func TestInsertOneIfPkIsPointRename(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type ID *int64 type TestPoint2 struct { Id ID `xorm:"autoincr pk notnull 'id'"` @@ -138,7 +140,7 @@ func TestInsertOneIfPkIsPointRename(t *testing.T) { } func TestInsert(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) user := Userinfo{0, "xiaolunwen", "dev", "lunny", time.Now(), @@ -156,7 +158,7 @@ func TestInsert(t *testing.T) { } func TestInsertAutoIncr(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) // auto increment insert @@ -177,7 +179,7 @@ type DefaultInsert struct { } func TestInsertDefault(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) di := new(DefaultInsert) err := testEngine.Sync2(di) @@ -203,7 +205,7 @@ type DefaultInsert2 struct { } func TestInsertDefault2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) di := new(DefaultInsert2) err := testEngine.Sync2(di) @@ -254,7 +256,7 @@ type CreatedInsert6 struct { } func TestInsertCreated(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) di := new(CreatedInsert) err := testEngine.Sync2(di) @@ -367,7 +369,7 @@ func TestDefaultTime3(t *testing.T) { Mtime JSONTime `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP updated" json:"mtime"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(PrepareTask)) prepareTask := &PrepareTask{ @@ -386,7 +388,7 @@ type MyJSONTime struct { } func TestCreatedJsonTime(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) di5 := new(MyJSONTime) err := testEngine.Sync2(di5) @@ -407,7 +409,7 @@ func TestCreatedJsonTime(t *testing.T) { } func TestInsertMulti2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) @@ -434,7 +436,7 @@ func TestInsertMulti2(t *testing.T) { } func TestInsertMulti2Interface(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) @@ -465,7 +467,7 @@ func TestInsertMulti2Interface(t *testing.T) { } func TestInsertTwoTable(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo), new(Userdetail)) @@ -480,7 +482,7 @@ func TestInsertTwoTable(t *testing.T) { } func TestInsertCreatedInt64(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestCreatedInt64 struct { Id int64 `xorm:"autoincr pk"` @@ -512,7 +514,7 @@ func (MyUserinfo) TableName() string { } func TestInsertMulti3(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) testEngine.ShowSQL(true) assertSync(t, new(MyUserinfo)) @@ -556,7 +558,7 @@ func (MyUserinfo2) TableName() string { } func TestInsertMulti4(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) testEngine.ShowSQL(false) assertSync(t, new(MyUserinfo2)) @@ -602,7 +604,7 @@ func TestAnonymousStruct(t *testing.T) { } `json:"ext" xorm:"'EXT' json notnull"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(PlainFoo)) _, err := testEngine.Insert(&PlainFoo{ @@ -631,7 +633,7 @@ func TestInsertMap(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(InsertMap)) cnt, err := testEngine.Table(new(InsertMap)).Insert(map[string]interface{}{ @@ -716,7 +718,7 @@ func TestInsertWhere(t *testing.T) { IsTrue bool } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(InsertWhere)) var i = InsertWhere{ @@ -811,7 +813,7 @@ func TestInsertWhere(t *testing.T) { } func TestInsertExpr2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type InsertExprsRelease struct { Id int64 @@ -877,7 +879,7 @@ func (NightlyRate) TableName() string { } func TestMultipleInsertTableName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) tableName := `prd_nightly_rate_16` assert.NoError(t, testEngine.Table(tableName).Sync2(new(NightlyRate))) @@ -908,7 +910,7 @@ func TestMultipleInsertTableName(t *testing.T) { } func TestInsertMultiWithOmit(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestMultiOmit struct { Id int64 `xorm:"int(11) pk"` @@ -951,7 +953,7 @@ func TestInsertMultiWithOmit(t *testing.T) { } func TestInsertTwice(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type InsertStructA struct { FieldA int @@ -977,7 +979,7 @@ func TestInsertTwice(t *testing.T) { assert.NoError(t, err) _, err = ssn.Insert(sliceA) - assert.EqualValues(t, ErrNoElementsOnSlice, err) + assert.EqualValues(t, xorm.ErrNoElementsOnSlice, err) _, err = ssn.Insert(sliceB) assert.NoError(t, err) diff --git a/session_iterate_test.go b/integrations/session_iterate_test.go similarity index 96% rename from session_iterate_test.go rename to integrations/session_iterate_test.go index bb0c59c4..564f457b 100644 --- a/session_iterate_test.go +++ b/integrations/session_iterate_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "testing" @@ -11,7 +11,7 @@ import ( ) func TestIterate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserIterate struct { Id int64 @@ -39,7 +39,7 @@ func TestIterate(t *testing.T) { } func TestBufferIterate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserBufferIterate struct { Id int64 diff --git a/session_pk_test.go b/integrations/session_pk_test.go similarity index 96% rename from session_pk_test.go rename to integrations/session_pk_test.go index 8a886603..d5f23491 100644 --- a/session_pk_test.go +++ b/integrations/session_pk_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "sort" @@ -66,7 +66,7 @@ type MyStringPK struct { } func TestIntId(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&IntId{}) assert.NoError(t, err) @@ -99,7 +99,7 @@ func TestIntId(t *testing.T) { } func TestInt16Id(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Int16Id{}) assert.NoError(t, err) @@ -132,7 +132,7 @@ func TestInt16Id(t *testing.T) { } func TestInt32Id(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Int32Id{}) assert.NoError(t, err) @@ -165,7 +165,7 @@ func TestInt32Id(t *testing.T) { } func TestUintId(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&UintId{}) assert.NoError(t, err) @@ -206,7 +206,7 @@ func TestUintId(t *testing.T) { } func TestUint16Id(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Uint16Id{}) assert.NoError(t, err) @@ -240,7 +240,7 @@ func TestUint16Id(t *testing.T) { } func TestUint32Id(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Uint32Id{}) assert.NoError(t, err) @@ -274,7 +274,7 @@ func TestUint32Id(t *testing.T) { } func TestUint64Id(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Uint64Id{}) assert.NoError(t, err) @@ -312,7 +312,7 @@ func TestUint64Id(t *testing.T) { } func TestStringPK(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&StringPK{}) assert.NoError(t, err) @@ -352,7 +352,7 @@ type CompositeKey struct { } func TestCompositeKey(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&CompositeKey{}) assert.NoError(t, err) @@ -407,7 +407,7 @@ func TestCompositeKey(t *testing.T) { } func TestCompositeKey2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type User struct { UserId string `xorm:"varchar(19) not null pk"` @@ -459,7 +459,7 @@ type UserPK2 struct { } func TestCompositeKey3(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&UserPK2{}) @@ -497,7 +497,7 @@ func TestCompositeKey3(t *testing.T) { } func TestMyIntId(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&MyIntPK{}) assert.NoError(t, err) @@ -535,7 +535,7 @@ func TestMyIntId(t *testing.T) { } func TestMyStringId(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&MyStringPK{}) assert.NoError(t, err) @@ -576,7 +576,7 @@ func TestSingleAutoIncrColumn(t *testing.T) { Id int64 `xorm:"pk autoincr"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Account)) _, err := testEngine.Insert(&Account{}) @@ -591,7 +591,7 @@ func TestCompositePK(t *testing.T) { Updated time.Time `xorm:"updated"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) tables1, err := testEngine.DBMetas() assert.NoError(t, err) @@ -626,7 +626,7 @@ func TestNoPKIdQueryUpdate(t *testing.T) { Username string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NoPKTable)) cnt, err := testEngine.Insert(&NoPKTable{ diff --git a/session_query_test.go b/integrations/session_query_test.go similarity index 96% rename from session_query_test.go rename to integrations/session_query_test.go index bed62be0..30f2e6ab 100644 --- a/session_query_test.go +++ b/integrations/session_query_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -17,7 +17,7 @@ import ( ) func TestQueryString(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetVar2 struct { Id int64 `xorm:"autoincr pk"` @@ -48,7 +48,7 @@ func TestQueryString(t *testing.T) { } func TestQueryString2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetVar3 struct { Id int64 `xorm:"autoincr pk"` @@ -108,7 +108,7 @@ func toFloat64(i interface{}) float64 { } func TestQueryInterface(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetVarInterface struct { Id int64 `xorm:"autoincr pk"` @@ -139,7 +139,7 @@ func TestQueryInterface(t *testing.T) { } func TestQueryNoParams(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type QueryNoParams struct { Id int64 `xorm:"autoincr pk"` @@ -188,7 +188,7 @@ func TestQueryNoParams(t *testing.T) { } func TestQueryStringNoParam(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetVar4 struct { Id int64 `xorm:"autoincr pk"` @@ -225,7 +225,7 @@ func TestQueryStringNoParam(t *testing.T) { } func TestQuerySliceStringNoParam(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetVar6 struct { Id int64 `xorm:"autoincr pk"` @@ -262,7 +262,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { } func TestQueryInterfaceNoParam(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type GetVar5 struct { Id int64 `xorm:"autoincr pk"` @@ -291,7 +291,7 @@ func TestQueryInterfaceNoParam(t *testing.T) { } func TestQueryWithBuilder(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type QueryWithBuilder struct { Id int64 `xorm:"autoincr pk"` @@ -336,7 +336,7 @@ func TestQueryWithBuilder(t *testing.T) { } func TestJoinWithSubQuery(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type JoinWithSubQuery1 struct { Id int64 `xorm:"autoincr pk"` diff --git a/session_raw_test.go b/integrations/session_raw_test.go similarity index 94% rename from session_raw_test.go rename to integrations/session_raw_test.go index 766206a4..8b9d6766 100644 --- a/session_raw_test.go +++ b/integrations/session_raw_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "strconv" @@ -12,7 +12,7 @@ import ( ) func TestExecAndQuery(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserinfoQuery struct { Uid int diff --git a/session_schema_test.go b/integrations/session_schema_test.go similarity index 93% rename from session_schema_test.go rename to integrations/session_schema_test.go index 37a1246b..005b6619 100644 --- a/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -13,7 +13,7 @@ import ( ) func TestStoreEngine(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.DropTables("user_store_engine")) @@ -26,7 +26,7 @@ func TestStoreEngine(t *testing.T) { } func TestCreateTable(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.DropTables("user_user")) @@ -39,7 +39,7 @@ func TestCreateTable(t *testing.T) { } func TestCreateMultiTables(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) session := testEngine.NewSession() defer session.Close() @@ -94,7 +94,7 @@ func (s *SyncTable3) TableName() string { } func TestSyncTable(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(SyncTable1))) @@ -119,7 +119,7 @@ func TestSyncTable(t *testing.T) { } func TestSyncTable2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Table("sync_tablex").Sync2(new(SyncTable1))) @@ -144,7 +144,7 @@ func TestSyncTable2(t *testing.T) { } func TestIsTableExist(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) exist, err := testEngine.IsTableExist(new(CustomTableName)) assert.NoError(t, err) @@ -158,7 +158,7 @@ func TestIsTableExist(t *testing.T) { } func TestIsTableEmpty(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type NumericEmpty struct { Numeric float64 `xorm:"numeric(26,2)"` @@ -201,7 +201,7 @@ func (c *CustomTableName) TableName() string { } func TestCustomTableName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) c := new(CustomTableName) assert.NoError(t, testEngine.DropTables(c)) @@ -220,7 +220,7 @@ type IndexOrUnique struct { } func TestIndexAndUnique(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.CreateTables(&IndexOrUnique{})) @@ -236,7 +236,7 @@ func TestIndexAndUnique(t *testing.T) { } func TestMetaInfo(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(CustomTableName), new(IndexOrUnique))) tables, err := testEngine.DBMetas() @@ -248,7 +248,7 @@ func TestMetaInfo(t *testing.T) { } func TestCharst(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables("user_charset") assert.NoError(t, err) @@ -264,7 +264,7 @@ func TestSync2_1(t *testing.T) { Id_delete int8 `xorm:"null int default 1"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.DropTables("wx_test")) assert.NoError(t, testEngine.Sync2(new(WxTest))) @@ -281,7 +281,7 @@ func TestUnique_1(t *testing.T) { UpdatedAt time.Time `xorm:"updated"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.DropTables("user_unique")) assert.NoError(t, testEngine.Sync2(new(UserUnique))) @@ -297,7 +297,7 @@ func TestSync2_2(t *testing.T) { UserId int64 `xorm:"index"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) var tableNames = make(map[string]bool) for i := 0; i < 10; i++ { @@ -326,7 +326,7 @@ func TestSync2_Default(t *testing.T) { Name string `xorm:"default('my_name')"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestSync2Default)) assert.NoError(t, testEngine.Sync2(new(TestSync2Default))) } diff --git a/session_stats_test.go b/integrations/session_stats_test.go similarity index 95% rename from session_stats_test.go rename to integrations/session_stats_test.go index 1f11560b..47a64076 100644 --- a/session_stats_test.go +++ b/integrations/session_stats_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -23,7 +23,7 @@ func TestSum(t *testing.T) { Float float32 } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(SumStruct))) var ( @@ -82,7 +82,7 @@ func (s SumStructWithTableName) TableName() string { } func TestSumWithTableName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(SumStructWithTableName))) var ( @@ -132,7 +132,7 @@ func TestSumWithTableName(t *testing.T) { } func TestSumCustomColumn(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type SumStruct2 struct { Int int @@ -160,7 +160,7 @@ func TestSumCustomColumn(t *testing.T) { } func TestCount(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserinfoCount struct { Departname string @@ -196,7 +196,7 @@ func TestCount(t *testing.T) { } func TestSQLCount(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserinfoCount2 struct { Id int64 @@ -218,7 +218,7 @@ func TestSQLCount(t *testing.T) { } func TestCountWithOthers(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type CountWithOthers struct { Id int64 @@ -252,7 +252,7 @@ func (CountWithTableName) TableName() string { } func TestWithTableName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(CountWithTableName)) @@ -276,7 +276,7 @@ func TestWithTableName(t *testing.T) { } func TestCountWithSelectCols(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(CountWithTableName)) diff --git a/session_test.go b/integrations/session_test.go similarity index 89% rename from session_test.go rename to integrations/session_test.go index 968842c3..bdf3278d 100644 --- a/session_test.go +++ b/integrations/session_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "database/sql" @@ -12,7 +12,7 @@ import ( ) func TestClose(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) sess1 := testEngine.NewSession() sess1.Close() @@ -31,7 +31,7 @@ func TestNullFloatStruct(t *testing.T) { Amount MyNullFloat64 } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync2(new(MyNullFloatStruct))) _, err := testEngine.Insert(&MyNullFloatStruct{ @@ -45,7 +45,7 @@ func TestNullFloatStruct(t *testing.T) { } func TestMustLogSQL(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) testEngine.ShowSQL(false) defer testEngine.ShowSQL(true) diff --git a/session_tx_test.go b/integrations/session_tx_test.go similarity index 96% rename from session_tx_test.go rename to integrations/session_tx_test.go index d95adead..4cff5610 100644 --- a/session_tx_test.go +++ b/integrations/session_tx_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -15,7 +15,7 @@ import ( ) func TestTransaction(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) counter := func(t *testing.T) { @@ -48,7 +48,7 @@ func TestTransaction(t *testing.T) { } func TestCombineTransaction(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) counter := func() { @@ -81,7 +81,7 @@ func TestCombineTransaction(t *testing.T) { } func TestCombineTransactionSameMapper(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) @@ -124,7 +124,7 @@ func TestCombineTransactionSameMapper(t *testing.T) { } func TestMultipleTransaction(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type MultipleTransaction struct { Id int64 diff --git a/session_update_test.go b/integrations/session_update_test.go similarity index 96% rename from session_update_test.go rename to integrations/session_update_test.go index 5111222a..8800246c 100644 --- a/session_update_test.go +++ b/integrations/session_update_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -11,12 +11,13 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" ) func TestUpdateMap(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateTable struct { Id int64 @@ -46,7 +47,7 @@ func TestUpdateLimit(t *testing.T) { return } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateTable2 struct { Id int64 @@ -88,7 +89,7 @@ type ForUpdate struct { Name string } -func setupForUpdate(engine EngineInterface) error { +func setupForUpdate(engine xorm.EngineInterface) error { v := new(ForUpdate) err := testEngine.DropTables(v) if err != nil { @@ -218,7 +219,7 @@ func TestWithIn(t *testing.T) { Test bool `xorm:"Test"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assert.NoError(t, testEngine.Sync(new(temp3))) testEngine.Insert(&[]temp3{ @@ -270,7 +271,7 @@ type Article struct { } func TestUpdateMap2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(UpdateMustCols)) _, err := testEngine.Table("update_must_cols").Where("id =?", 1).Update(map[string]interface{}{ @@ -280,7 +281,7 @@ func TestUpdateMap2(t *testing.T) { } func TestUpdate1(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) _, err := testEngine.Insert(&Userinfo{ @@ -410,7 +411,7 @@ func TestUpdate1(t *testing.T) { } func TestUpdateIncrDecr(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) col1 := &UpdateIncr{ Name: "test", @@ -473,7 +474,7 @@ type UpdatedUpdate5 struct { } func TestUpdateUpdated(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) di := new(UpdatedUpdate) err := testEngine.Sync2(di) @@ -568,7 +569,7 @@ func TestUpdateUpdated(t *testing.T) { } func TestUpdateSameMapper(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) oldMapper := testEngine.GetTableMapper() testEngine.UnMapType(utils.ReflectValue(new(Userinfo)).Type()) @@ -695,7 +696,7 @@ func TestUpdateSameMapper(t *testing.T) { } func TestUseBool(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) cnt1, err := testEngine.Count(&Userinfo{}) @@ -725,7 +726,7 @@ func TestUseBool(t *testing.T) { } func TestBool(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(Userinfo)) _, err := testEngine.UseBool().Update(&Userinfo{IsMan: true}) @@ -748,7 +749,7 @@ func TestBool(t *testing.T) { } func TestNoUpdate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type NoUpdate struct { Id int64 @@ -769,7 +770,7 @@ func TestNoUpdate(t *testing.T) { } func TestNewUpdate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TbUserInfo struct { Id int64 `xorm:"pk autoincr unique BIGINT" json:"id"` @@ -799,7 +800,7 @@ func TestNewUpdate(t *testing.T) { } func TestUpdateUpdate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type PublicKeyUpdate struct { Id int64 @@ -816,7 +817,7 @@ func TestUpdateUpdate(t *testing.T) { } func TestCreatedUpdated2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type CreatedUpdatedStruct struct { Id int64 @@ -860,7 +861,7 @@ func TestCreatedUpdated2(t *testing.T) { } func TestDeletedUpdate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DeletedUpdatedStruct struct { Id int64 @@ -908,7 +909,7 @@ func TestDeletedUpdate(t *testing.T) { } func TestUpdateMapCondition(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateMapCondition struct { Id int64 @@ -939,7 +940,7 @@ func TestUpdateMapCondition(t *testing.T) { } func TestUpdateMapContent(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateMapContent struct { Id int64 @@ -1014,7 +1015,7 @@ func TestUpdateCondiBean(t *testing.T) { Name string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NeedUpdateBean)) cnt, err := testEngine.Insert(&NeedUpdateBean{ @@ -1064,7 +1065,7 @@ func TestWhereCondErrorWhenUpdate(t *testing.T) { RequestToken string } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(AuthRequestError)) _, err := testEngine.Cols("challenge_token", "request_token", "challenge_agent", "status"). @@ -1073,11 +1074,11 @@ func TestWhereCondErrorWhenUpdate(t *testing.T) { ChallengeToken: "2", }) assert.Error(t, err) - assert.EqualValues(t, ErrConditionType, err) + assert.EqualValues(t, xorm.ErrConditionType, err) } func TestUpdateDeleted(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateDeletedStruct struct { Id int64 @@ -1118,7 +1119,7 @@ func TestUpdateDeleted(t *testing.T) { } func TestUpdateExprs(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateExprs struct { Id int64 @@ -1149,7 +1150,7 @@ func TestUpdateExprs(t *testing.T) { } func TestUpdateAlias(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateAlias struct { Id int64 @@ -1180,7 +1181,7 @@ func TestUpdateAlias(t *testing.T) { } func TestUpdateExprs2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateExprsRelease struct { Id int64 @@ -1225,7 +1226,7 @@ func TestUpdateExprs2(t *testing.T) { } func TestUpdateMap3(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UpdateMapUser struct { Id uint64 `xorm:"PK autoincr"` @@ -1283,7 +1284,7 @@ func TestUpdateIgnoreOnlyFromDBFields(t *testing.T) { return &record } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestOnlyFromDBField)) _, err := testEngine.Insert(&TestOnlyFromDBField{ @@ -1311,7 +1312,7 @@ func TestUpdateMultiplePK(t *testing.T) { Value string `xorm:"notnull varchar(4000)" description:"值"` } - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(TestUpdateMultiplePKStruct)) test := &TestUpdateMultiplePKStruct{ diff --git a/tags_test.go b/integrations/tags_test.go similarity index 96% rename from tags_test.go rename to integrations/tags_test.go index c2a56290..f787fffe 100644 --- a/tags_test.go +++ b/integrations/tags_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -60,7 +60,7 @@ type UserAndDetail struct { } func TestExtends(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&tempUser2{}) assert.NoError(t, err) @@ -211,7 +211,7 @@ type MessageExtend4 struct { } func TestExtends2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) assert.NoError(t, err) @@ -272,7 +272,7 @@ func TestExtends2(t *testing.T) { } func TestExtends3(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) assert.NoError(t, err) @@ -337,7 +337,7 @@ func TestExtends3(t *testing.T) { } func TestExtends4(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{}) assert.NoError(t, err) @@ -410,7 +410,7 @@ type Book struct { } func TestExtends5(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&Book{}, &Size{}) assert.NoError(t, err) @@ -508,7 +508,7 @@ func TestExtends5(t *testing.T) { } func TestCacheTag(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type CacheDomain struct { Id int64 `xorm:"pk cache"` @@ -520,7 +520,7 @@ func TestCacheTag(t *testing.T) { } func TestNoCacheTag(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type NoCacheDomain struct { Id int64 `xorm:"pk nocache"` @@ -536,7 +536,7 @@ type IDGonicMapper struct { } func TestGonicMapperID(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(utils.ReflectValue(new(IDGonicMapper)).Type()) @@ -573,7 +573,7 @@ type IDSameMapper struct { } func TestSameMapperID(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(utils.ReflectValue(new(IDSameMapper)).Type()) @@ -612,7 +612,7 @@ type UserCU struct { } func TestCreatedAndUpdated(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) u := new(UserCU) err := testEngine.DropTables(u) @@ -645,7 +645,7 @@ type StrangeName struct { } func TestStrangeName(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(new(StrangeName)) assert.NoError(t, err) @@ -662,7 +662,7 @@ func TestStrangeName(t *testing.T) { } func TestCreatedUpdated(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type CreatedUpdated struct { Id int64 @@ -692,7 +692,7 @@ func TestCreatedUpdated(t *testing.T) { } func TestCreatedUpdatedInt64(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type CreatedUpdatedInt64 struct { Id int64 @@ -726,7 +726,7 @@ type Lowercase struct { } func TestLowerCase(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.Sync2(&Lowercase{}) assert.NoError(t, err) @@ -743,7 +743,7 @@ func TestLowerCase(t *testing.T) { } func TestAutoIncrTag(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestAutoIncr1 struct { Id int64 @@ -799,7 +799,7 @@ func TestAutoIncrTag(t *testing.T) { } func TestTagComment(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) // FIXME: only support mysql if testEngine.Dialect().URI().DBType != schemas.MYSQL { return @@ -833,7 +833,7 @@ func TestTagComment(t *testing.T) { } func TestTagDefault(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DefaultStruct struct { Id int64 @@ -877,7 +877,7 @@ func TestTagDefault(t *testing.T) { } func TestTagDefault2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DefaultStruct2 struct { Id int64 @@ -906,7 +906,7 @@ func TestTagDefault2(t *testing.T) { } func TestTagDefault3(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DefaultStruct3 struct { Id int64 @@ -935,7 +935,7 @@ func TestTagDefault3(t *testing.T) { } func TestTagDefault4(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DefaultStruct4 struct { Id int64 @@ -967,7 +967,7 @@ func TestTagDefault4(t *testing.T) { } func TestTagDefault5(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DefaultStruct5 struct { Id int64 @@ -1003,7 +1003,7 @@ func TestTagDefault5(t *testing.T) { } func TestTagDefault6(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DefaultStruct6 struct { Id int64 @@ -1037,7 +1037,7 @@ func TestTagDefault6(t *testing.T) { } func TestTagsDirection(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type OnlyFromDBStruct struct { Id int64 @@ -1099,7 +1099,7 @@ func TestTagsDirection(t *testing.T) { } func TestTagTime(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TagUTCStruct struct { Id int64 @@ -1133,7 +1133,7 @@ func TestTagTime(t *testing.T) { } func TestTagAutoIncr(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TagAutoIncr struct { Id int64 @@ -1158,7 +1158,7 @@ func TestTagAutoIncr(t *testing.T) { } func TestTagPrimarykey(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TagPrimaryKey struct { Id int64 `xorm:"pk"` Name string `xorm:"VARCHAR(20) pk"` @@ -1189,7 +1189,7 @@ type VersionS struct { } func TestVersion1(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(new(VersionS)) assert.NoError(t, err) @@ -1220,7 +1220,7 @@ func TestVersion1(t *testing.T) { } func TestVersion2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(new(VersionS)) assert.NoError(t, err) @@ -1247,7 +1247,7 @@ type VersionUintS struct { } func TestVersion3(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(new(VersionUintS)) assert.NoError(t, err) @@ -1278,7 +1278,7 @@ func TestVersion3(t *testing.T) { } func TestVersion4(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(new(VersionUintS)) assert.NoError(t, err) @@ -1298,7 +1298,7 @@ func TestVersion4(t *testing.T) { } func TestIndexes(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TestIndexesStruct struct { Id int64 diff --git a/xorm_test.go b/integrations/tests.go similarity index 89% rename from xorm_test.go rename to integrations/tests.go index c9a1a74b..c8219935 100644 --- a/xorm_test.go +++ b/integrations/tests.go @@ -1,8 +1,8 @@ -// Copyright 2018 The Xorm Authors. All rights reserved. +// 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 +package integrations import ( "database/sql" @@ -12,11 +12,7 @@ import ( "strings" "testing" - _ "github.com/denisenkom/go-mssqldb" - _ "github.com/go-sql-driver/mysql" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" - _ "github.com/ziutek/mymysql/godrv" + "xorm.io/xorm" "xorm.io/xorm/caches" "xorm.io/xorm/dialects" "xorm.io/xorm/log" @@ -25,7 +21,7 @@ import ( ) var ( - testEngine EngineInterface + testEngine xorm.EngineInterface dbType string connString string @@ -102,9 +98,9 @@ func createEngine(dbType, connStr string) error { *ignoreSelectUpdate = true } - testEngine, err = NewEngine(dbType, connStr) + testEngine, err = xorm.NewEngine(dbType, connStr) } else { - testEngine, err = NewEngineGroup(dbType, strings.Split(connStr, *splitter)) + testEngine, err = xorm.NewEngineGroup(dbType, strings.Split(connStr, *splitter)) if dbType != "mysql" && dbType != "mymysql" { *ignoreSelectUpdate = true } @@ -160,11 +156,11 @@ func createEngine(dbType, connStr string) error { return nil } -func prepareEngine() error { +func PrepareEngine() error { return createEngine(dbType, connString) } -func TestMain(m *testing.M) { +func MainTest(m *testing.M) { flag.Parse() dbType = *db @@ -192,7 +188,7 @@ func TestMain(m *testing.M) { testEngine = nil fmt.Println("testing", dbType, connString) - if err := prepareEngine(); err != nil { + if err := PrepareEngine(); err != nil { fmt.Println(err) os.Exit(1) return @@ -206,9 +202,3 @@ func TestMain(m *testing.M) { os.Exit(res) } - -func TestPing(t *testing.T) { - if err := testEngine.Ping(); err != nil { - t.Fatal(err) - } -} diff --git a/time_test.go b/integrations/time_test.go similarity index 97% rename from time_test.go rename to integrations/time_test.go index 609d7882..6d8d812c 100644 --- a/time_test.go +++ b/integrations/time_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "fmt" @@ -20,7 +20,7 @@ func formatTime(t time.Time) string { } func TestTimeUserTime(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type TimeUser struct { Id string @@ -50,7 +50,7 @@ func TestTimeUserTime(t *testing.T) { } func TestTimeUserTimeDiffLoc(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) testEngine.SetTZLocation(loc) @@ -86,7 +86,7 @@ func TestTimeUserTimeDiffLoc(t *testing.T) { } func TestTimeUserCreated(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserCreated struct { Id string @@ -115,7 +115,7 @@ func TestTimeUserCreated(t *testing.T) { } func TestTimeUserCreatedDiffLoc(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) testEngine.SetTZLocation(loc) @@ -150,7 +150,7 @@ func TestTimeUserCreatedDiffLoc(t *testing.T) { } func TestTimeUserUpdated(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserUpdated struct { Id string @@ -201,7 +201,7 @@ func TestTimeUserUpdated(t *testing.T) { } func TestTimeUserUpdatedDiffLoc(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) testEngine.SetTZLocation(loc) @@ -258,7 +258,7 @@ func TestTimeUserUpdatedDiffLoc(t *testing.T) { } func TestTimeUserDeleted(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserDeleted struct { Id string @@ -308,7 +308,7 @@ func TestTimeUserDeleted(t *testing.T) { } func TestTimeUserDeletedDiffLoc(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) testEngine.SetTZLocation(loc) @@ -385,7 +385,7 @@ func (j *JSONDate) Unix() int64 { } func TestCustomTimeUserDeleted(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type UserDeleted3 struct { Id string @@ -432,7 +432,7 @@ func TestCustomTimeUserDeleted(t *testing.T) { } func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) loc, err := time.LoadLocation("Asia/Shanghai") assert.NoError(t, err) testEngine.SetTZLocation(loc) @@ -485,7 +485,7 @@ func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { } func TestDeletedInt64(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type DeletedInt64Struct struct { Id int64 diff --git a/types_null_test.go b/integrations/types_null_test.go similarity index 94% rename from types_null_test.go rename to integrations/types_null_test.go index f62366b7..98bd86b9 100644 --- a/types_null_test.go +++ b/integrations/types_null_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "database/sql" @@ -58,19 +58,19 @@ func (m CustomStruct) Value() (driver.Value, error) { } func TestCreateNullStructTable(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.CreateTables(new(NullType)) assert.NoError(t, err) } func TestDropNullStructTable(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(new(NullType)) assert.NoError(t, err) } func TestNullStructInsert(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) item1 := new(NullType) @@ -112,7 +112,7 @@ func TestNullStructInsert(t *testing.T) { } func TestNullStructUpdate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) _, err := testEngine.Insert([]NullType{ @@ -185,7 +185,7 @@ func TestNullStructUpdate(t *testing.T) { } func TestNullStructFind(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) _, err := testEngine.Insert([]NullType{ @@ -250,7 +250,7 @@ func TestNullStructFind(t *testing.T) { } func TestNullStructIterate(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) if true { @@ -265,7 +265,7 @@ func TestNullStructIterate(t *testing.T) { } func TestNullStructCount(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) if true { @@ -276,7 +276,7 @@ func TestNullStructCount(t *testing.T) { } func TestNullStructRows(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) item := new(NullType) @@ -291,7 +291,7 @@ func TestNullStructRows(t *testing.T) { } func TestNullStructDelete(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) assertSync(t, new(NullType)) item := new(NullType) diff --git a/types_test.go b/integrations/types_test.go similarity index 96% rename from types_test.go rename to integrations/types_test.go index 7cabb10d..112308f3 100644 --- a/types_test.go +++ b/integrations/types_test.go @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package integrations import ( "errors" "fmt" "testing" + "xorm.io/xorm" "xorm.io/xorm/convert" "xorm.io/xorm/internal/json" "xorm.io/xorm/schemas" @@ -17,7 +18,7 @@ import ( ) func TestArrayField(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type ArrayStruct struct { Id int64 @@ -80,7 +81,7 @@ func TestArrayField(t *testing.T) { } func TestGetBytes(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) type Varbinary struct { Data []byte `xorm:"VARBINARY(250)"` @@ -152,14 +153,14 @@ type ConvStruct struct { Slice SliceType } -func (c *ConvStruct) BeforeSet(name string, cell Cell) { +func (c *ConvStruct) BeforeSet(name string, cell xorm.Cell) { if name == "cfg3" || name == "Cfg3" { c.Cfg3 = new(ConvConfig) } } func TestConversion(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) c := new(ConvStruct) assert.NoError(t, testEngine.DropTables(c)) @@ -243,7 +244,7 @@ type MyStruct struct { } func TestCustomType1(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) err := testEngine.DropTables(&MyStruct{}) assert.NoError(t, err) @@ -331,7 +332,7 @@ type UserCus struct { } func TestCustomType2(t *testing.T) { - assert.NoError(t, prepareEngine()) + assert.NoError(t, PrepareEngine()) var uc UserCus err := testEngine.CreateTables(&uc) diff --git a/xorm.go b/xorm.go deleted file mode 100644 index 2fe32e50..00000000 --- a/xorm.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2015 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 go1.11 - -package xorm - -import ( - "context" - "os" - "runtime" - "time" - - "xorm.io/xorm/caches" - "xorm.io/xorm/core" - "xorm.io/xorm/dialects" - "xorm.io/xorm/log" - "xorm.io/xorm/names" - "xorm.io/xorm/schemas" - "xorm.io/xorm/tags" -) - -func close(engine *Engine) { - engine.Close() -} - -// NewEngine new a db manager according to the parameter. Currently support four -// drivers -func NewEngine(driverName string, dataSourceName string) (*Engine, error) { - dialect, err := dialects.OpenDialect(driverName, dataSourceName) - if err != nil { - return nil, err - } - - db, err := core.Open(driverName, dataSourceName) - if err != nil { - return nil, err - } - - cacherMgr := caches.NewManager() - mapper := names.NewCacheMapper(new(names.SnakeMapper)) - tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) - - engine := &Engine{ - dialect: dialect, - TZLocation: time.Local, - defaultContext: context.Background(), - cacherMgr: cacherMgr, - tagParser: tagParser, - driverName: driverName, - dataSourceName: dataSourceName, - db: db, - logSessionID: false, - } - - if dialect.URI().DBType == schemas.SQLITE { - engine.DatabaseTZ = time.UTC - } else { - engine.DatabaseTZ = time.Local - } - - logger := log.NewSimpleLogger(os.Stdout) - logger.SetLevel(log.LOG_INFO) - engine.SetLogger(log.NewLoggerAdapter(logger)) - - runtime.SetFinalizer(engine, close) - - return engine, nil -} - -// NewEngineWithParams new a db manager with params. The params will be passed to dialects. -func NewEngineWithParams(driverName string, dataSourceName string, params map[string]string) (*Engine, error) { - engine, err := NewEngine(driverName, dataSourceName) - engine.dialect.SetParams(params) - return engine, err -} - -// Clone clone an engine -func (engine *Engine) Clone() (*Engine, error) { - return NewEngine(engine.DriverName(), engine.DataSourceName()) -} From 0a06dc204a4093cf1ae4cbe7c46a97c5a98fac66 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 28 Mar 2020 02:23:37 +0000 Subject: [PATCH 096/112] Move processor function into one file (#1637) Move processor function into one file Reviewed-on: https://gitea.com/xorm/xorm/pulls/1637 --- processors.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++ session.go | 53 +++---------------------------------- session_delete.go | 6 +---- session_insert.go | 4 +-- 4 files changed, 73 insertions(+), 56 deletions(-) diff --git a/processors.go b/processors.go index dcd9c6ac..8697e302 100644 --- a/processors.go +++ b/processors.go @@ -76,3 +76,69 @@ func (session *Session) executeProcessors() error { } return nil } + +func cleanupProcessorsClosures(slices *[]func(interface{})) { + if len(*slices) > 0 { + *slices = make([]func(interface{}), 0) + } +} + +func executeBeforeClosures(session *Session, bean interface{}) { + // handle before delete processors + for _, closure := range session.beforeClosures { + closure(bean) + } + cleanupProcessorsClosures(&session.beforeClosures) +} + +func executeBeforeSet(bean interface{}, fields []string, scanResults []interface{}) { + if b, hasBeforeSet := bean.(BeforeSetProcessor); hasBeforeSet { + for ii, key := range fields { + b.BeforeSet(key, Cell(scanResults[ii].(*interface{}))) + } + } +} + +func executeAfterSet(bean interface{}, fields []string, scanResults []interface{}) { + if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { + for ii, key := range fields { + b.AfterSet(key, Cell(scanResults[ii].(*interface{}))) + } + } +} + +func buildAfterProcessors(session *Session, bean interface{}) { + // handle afterClosures + for _, closure := range session.afterClosures { + session.afterProcessors = append(session.afterProcessors, executedProcessor{ + fun: func(sess *Session, bean interface{}) error { + closure(bean) + return nil + }, + session: session, + bean: bean, + }) + } + + if a, has := bean.(AfterLoadProcessor); has { + session.afterProcessors = append(session.afterProcessors, executedProcessor{ + fun: func(sess *Session, bean interface{}) error { + a.AfterLoad() + return nil + }, + session: session, + bean: bean, + }) + } + + if a, has := bean.(AfterLoadSessionProcessor); has { + session.afterProcessors = append(session.afterProcessors, executedProcessor{ + fun: func(sess *Session, bean interface{}) error { + a.AfterLoad(sess) + return nil + }, + session: session, + bean: bean, + }) + } +} diff --git a/session.go b/session.go index 81afcc03..9f47d9b4 100644 --- a/session.go +++ b/session.go @@ -328,12 +328,6 @@ func (session *Session) DB() *core.DB { return session.db() } -func cleanupProcessorsClosures(slices *[]func(interface{})) { - if len(*slices) > 0 { - *slices = make([]func(interface{}), 0) - } -} - func (session *Session) canCache() bool { if session.statement.RefTable == nil || session.statement.JoinStr != "" || @@ -425,56 +419,17 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa return nil, err } - if b, hasBeforeSet := bean.(BeforeSetProcessor); hasBeforeSet { - for ii, key := range fields { - b.BeforeSet(key, Cell(scanResults[ii].(*interface{}))) - } - } + executeBeforeSet(bean, fields, scanResults) + return scanResults, nil } func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { defer func() { - if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { - for ii, key := range fields { - b.AfterSet(key, Cell(scanResults[ii].(*interface{}))) - } - } + executeAfterSet(bean, fields, scanResults) }() - // handle afterClosures - for _, closure := range session.afterClosures { - session.afterProcessors = append(session.afterProcessors, executedProcessor{ - fun: func(sess *Session, bean interface{}) error { - closure(bean) - return nil - }, - session: session, - bean: bean, - }) - } - - if a, has := bean.(AfterLoadProcessor); has { - session.afterProcessors = append(session.afterProcessors, executedProcessor{ - fun: func(sess *Session, bean interface{}) error { - a.AfterLoad() - return nil - }, - session: session, - bean: bean, - }) - } - - if a, has := bean.(AfterLoadSessionProcessor); has { - session.afterProcessors = append(session.afterProcessors, executedProcessor{ - fun: func(sess *Session, bean interface{}) error { - a.AfterLoad(sess) - return nil - }, - session: session, - bean: bean, - }) - } + buildAfterProcessors(session, bean) var tempMap = make(map[string]int) var pk schemas.PK diff --git a/session_delete.go b/session_delete.go index ff28867a..13bf791f 100644 --- a/session_delete.go +++ b/session_delete.go @@ -96,11 +96,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { return 0, err } - // handle before delete processors - for _, closure := range session.beforeClosures { - closure(bean) - } - cleanupProcessorsClosures(&session.beforeClosures) + executeBeforeClosures(session, bean) if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok { processor.BeforeDelete() diff --git a/session_insert.go b/session_insert.go index 2c46a59b..5f968151 100644 --- a/session_insert.go +++ b/session_insert.go @@ -233,7 +233,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error for _, closure := range session.afterClosures { closure(elemValue) } - if processor, ok := interface{}(elemValue).(AfterInsertProcessor); ok { + if processor, ok := elemValue.(AfterInsertProcessor); ok { processor.AfterInsert() } } else { @@ -246,7 +246,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error session.afterInsertBeans[elemValue] = &afterClosures } } else { - if _, ok := interface{}(elemValue).(AfterInsertProcessor); ok { + if _, ok := elemValue.(AfterInsertProcessor); ok { session.afterInsertBeans[elemValue] = nil } } From 5d90695511061b579fccfc7061838e4bd1ebf88f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 28 Mar 2020 02:24:39 +0000 Subject: [PATCH 097/112] update discuss forum new address (#1638) update discuss forum new address Reviewed-on: https://gitea.com/xorm/xorm/pulls/1638 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7334e6d4..a15be488 100644 --- a/README.md +++ b/README.md @@ -420,7 +420,7 @@ res, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) ## Contributing -If you want to pull request, please see [CONTRIBUTING](https://gitea.com/xorm/xorm/src/branch/master/CONTRIBUTING.md). And we also provide [Xorm on Google Groups](https://groups.google.com/forum/#!forum/xorm) to discuss. +If you want to pull request, please see [CONTRIBUTING](https://gitea.com/xorm/xorm/src/branch/master/CONTRIBUTING.md). And you can also go to [Xorm on discourse](https://xorm.discourse.group) to discuss. ## Credits From 410c2de923f905ee92692d453fccd9de1dcc5364 Mon Sep 17 00:00:00 2001 From: "limo.creed" Date: Wed, 1 Apr 2020 12:37:03 +0000 Subject: [PATCH 098/112] fix misspelled words (#1642) fix misspelled words Co-authored-by: yuxiao.lu Reviewed-on: https://gitea.com/xorm/xorm/pulls/1642 --- log/logger_context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log/logger_context.go b/log/logger_context.go index c2d94dc6..faed26d6 100644 --- a/log/logger_context.go +++ b/log/logger_context.go @@ -51,7 +51,7 @@ var ( SessionShowSQLKey = "__xorm_show_sql" ) -// LoggerAdapter wraps a Logger interafce as LoggerContext interface +// LoggerAdapter wraps a Logger interface as LoggerContext interface type LoggerAdapter struct { logger Logger } From 7e981f5f5e392d28cb700449bc03b893d6f04794 Mon Sep 17 00:00:00 2001 From: rosbit Date: Sun, 5 Apr 2020 04:49:44 +0000 Subject: [PATCH 099/112] fix bug for mis-converting uint64 to int64 (#1647) fix bug for mis-converting uint64 to int64 Co-authored-by: rosbit Reviewed-on: https://gitea.com/xorm/xorm/pulls/1647 --- internal/statements/statement.go | 3 +-- internal/statements/update.go | 3 +-- internal/statements/values.go | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 22625650..ed7bdaeb 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -797,8 +797,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, if !requiredField && fieldValue.Uint() == 0 { continue } - t := int64(fieldValue.Uint()) - val = reflect.ValueOf(&t).Interface() + val = fieldValue.Interface() case reflect.Struct: if fieldType.ConvertibleTo(schemas.TimeType) { t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) diff --git a/internal/statements/update.go b/internal/statements/update.go index 2bd7ddd3..b6ae118e 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -190,8 +190,7 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, if !requiredField && fieldValue.Uint() == 0 { continue } - t := int64(fieldValue.Uint()) - val = reflect.ValueOf(&t).Interface() + val = fieldValue.Interface() case reflect.Struct: if fieldType.ConvertibleTo(schemas.TimeType) { t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) diff --git a/internal/statements/values.go b/internal/statements/values.go index 0ab174d6..a1102c54 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -147,7 +147,7 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl } return nil, ErrUnSupportedType case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - return int64(fieldValue.Uint()), nil + return fieldValue.Uint(), nil default: return fieldValue.Interface(), nil } From cfa88b908c42f1079f9e4ac4390468474b2b709f Mon Sep 17 00:00:00 2001 From: "limo.creed" Date: Wed, 8 Apr 2020 02:20:36 +0000 Subject: [PATCH 100/112] fix context not deliver in QueryStructContext (#1645) fix context not deliver in QueryStructContext Co-authored-by: yuxiao.lu Reviewed-on: https://gitea.com/xorm/xorm/pulls/1645 Reviewed-by: Lunny Xiao --- core/stmt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stmt.go b/core/stmt.go index 4b1c7605..ebf2af73 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -175,7 +175,7 @@ func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, e args[i] = vv.Elem().FieldByName(k).Interface() } - return s.Query(args...) + return s.QueryContext(ctx, args...) } func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { From 34dc7f879184697c08ed680f4b011cca28c09f76 Mon Sep 17 00:00:00 2001 From: "limo.creed" Date: Thu, 9 Apr 2020 06:03:39 +0000 Subject: [PATCH 101/112] Add Hook (#1644) move hook to standalone package add hook for engine Co-authored-by: yuxiao.lu Reviewed-on: https://gitea.com/xorm/xorm/pulls/1644 Reviewed-by: Lunny Xiao --- contexts/hook.go | 75 ++++++++++++++++++++++ contexts/hook_test.go | 140 ++++++++++++++++++++++++++++++++++++++++++ core/db.go | 77 ++++++++++++----------- core/stmt.go | 78 +++++++---------------- core/tx.go | 95 ++++++++-------------------- engine.go | 7 ++- engine_group.go | 10 ++- interface.go | 2 + log/logger_context.go | 12 +--- session.go | 2 +- 10 files changed, 324 insertions(+), 174 deletions(-) create mode 100644 contexts/hook.go create mode 100644 contexts/hook_test.go diff --git a/contexts/hook.go b/contexts/hook.go new file mode 100644 index 00000000..71ad8e87 --- /dev/null +++ b/contexts/hook.go @@ -0,0 +1,75 @@ +// Copyright 2020 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 contexts + +import ( + "context" + "database/sql" + "time" +) + +// ContextHook represents a hook context +type ContextHook struct { + start time.Time + Ctx context.Context + SQL string // log content or SQL + Args []interface{} // if it's a SQL, it's the arguments + Result sql.Result + ExecuteTime time.Duration + Err error // SQL executed error +} + +// NewContextHook return context for hook +func NewContextHook(ctx context.Context, sql string, args []interface{}) *ContextHook { + return &ContextHook{ + start: time.Now(), + Ctx: ctx, + SQL: sql, + Args: args, + } +} + +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) +} + +type Hook interface { + BeforeProcess(c *ContextHook) (context.Context, error) + AfterProcess(c *ContextHook) error +} + +type Hooks struct { + hooks []Hook +} + +func (h *Hooks) AddHook(hooks ...Hook) { + h.hooks = append(h.hooks, hooks...) +} + +func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) { + ctx := c.Ctx + for _, h := range h.hooks { + var err error + ctx, err = h.BeforeProcess(c) + if err != nil { + return nil, err + } + } + return ctx, nil +} + +func (h *Hooks) AfterProcess(c *ContextHook) error { + firstErr := c.Err + for _, h := range h.hooks { + err := h.AfterProcess(c) + if err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} diff --git a/contexts/hook_test.go b/contexts/hook_test.go new file mode 100644 index 00000000..96c54e33 --- /dev/null +++ b/contexts/hook_test.go @@ -0,0 +1,140 @@ +package contexts + +import ( + "context" + "errors" + "testing" +) + +type testHook struct { + before func(c *ContextHook) (context.Context, error) + after func(c *ContextHook) error +} + +func (h *testHook) BeforeProcess(c *ContextHook) (context.Context, error) { + if h.before != nil { + return h.before(c) + } + return c.Ctx, nil +} + +func (h *testHook) AfterProcess(c *ContextHook) error { + if h.after != nil { + return h.after(c) + } + return c.Err +} + +var _ Hook = &testHook{} + +func TestBeforeProcess(t *testing.T) { + expectErr := errors.New("before error") + tests := []struct { + msg string + hooks []Hook + expect error + }{ + { + msg: "first hook return err", + hooks: []Hook{ + &testHook{ + before: func(c *ContextHook) (ctx context.Context, err error) { + return c.Ctx, expectErr + }, + }, + &testHook{ + before: func(c *ContextHook) (ctx context.Context, err error) { + return c.Ctx, nil + }, + }, + }, + expect: expectErr, + }, + { + msg: "second hook return err", + hooks: []Hook{ + &testHook{ + before: func(c *ContextHook) (ctx context.Context, err error) { + return c.Ctx, nil + }, + }, + &testHook{ + before: func(c *ContextHook) (ctx context.Context, err error) { + return c.Ctx, expectErr + }, + }, + }, + expect: expectErr, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + hooks := Hooks{} + hooks.AddHook(tt.hooks...) + _, err := hooks.BeforeProcess(&ContextHook{ + Ctx: context.Background(), + }) + if err != tt.expect { + t.Errorf("got %v, expect %v", err, tt.expect) + } + }) + } +} + +func TestAfterProcess(t *testing.T) { + expectErr := errors.New("expect err") + tests := []struct { + msg string + ctx *ContextHook + hooks []Hook + expect error + }{ + { + msg: "context has err", + ctx: &ContextHook{ + Ctx: context.Background(), + Err: expectErr, + }, + hooks: []Hook{ + &testHook{ + after: func(c *ContextHook) error { + return errors.New("hook err") + }, + }, + }, + expect: expectErr, + }, + { + msg: "last hook has err", + ctx: &ContextHook{ + Ctx: context.Background(), + Err: nil, + }, + hooks: []Hook{ + &testHook{ + after: func(c *ContextHook) error { + return nil + }, + }, + &testHook{ + after: func(c *ContextHook) error { + return expectErr + }, + }, + }, + expect: expectErr, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + hooks := Hooks{} + hooks.AddHook(tt.hooks...) + err := hooks.AfterProcess(tt.ctx) + if err != tt.expect { + t.Errorf("got %v, expect %v", err, tt.expect) + } + }) + } +} diff --git a/core/db.go b/core/db.go index 671d1dc2..50c64c6f 100644 --- a/core/db.go +++ b/core/db.go @@ -12,8 +12,8 @@ import ( "reflect" "regexp" "sync" - "time" + "xorm.io/xorm/contexts" "xorm.io/xorm/log" "xorm.io/xorm/names" ) @@ -88,6 +88,7 @@ type DB struct { reflectCache map[reflect.Type]*cacheStruct reflectCacheMutex sync.RWMutex Logger log.ContextLogger + hooks contexts.Hooks } // Open opens a database @@ -140,26 +141,14 @@ func (db *DB) reflectNew(typ reflect.Type) reflect.Value { // QueryContext overwrites sql.DB.QueryContext func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { - start := time.Now() - showSQL := db.NeedLogSQL(ctx) - if showSQL { - db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - }) + hookCtx := contexts.NewContextHook(ctx, query, args) + ctx, err := db.beforeProcess(hookCtx) + if err != nil { + return nil, err } rows, err := db.DB.QueryContext(ctx, query, args...) - if showSQL { - db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - if err != nil { + hookCtx.End(ctx, nil, err) + if err := db.afterProcess(hookCtx); err != nil { if rows != nil { rows.Close() } @@ -239,7 +228,7 @@ var ( re = regexp.MustCompile(`[?](\w+)`) ) -// ExecMapContext exec map with context.Context +// ExecMapContext exec map with context.ContextHook // insert into (name) values (?) // insert into (name) values (?name) func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { @@ -263,28 +252,42 @@ func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{ } func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - start := time.Now() - showSQL := db.NeedLogSQL(ctx) - if showSQL { - db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - }) + hookCtx := contexts.NewContextHook(ctx, query, args) + ctx, err := db.beforeProcess(hookCtx) + if err != nil { + return nil, err } res, err := db.DB.ExecContext(ctx, query, args...) - if showSQL { - db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - ExecuteTime: time.Now().Sub(start), - Err: err, - }) + hookCtx.End(ctx, res, err) + if err := db.afterProcess(hookCtx); err != nil { + return nil, err } - return res, err + return res, nil } func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { return db.ExecStructContext(context.Background(), query, st) } + +func (db *DB) beforeProcess(c *contexts.ContextHook) (context.Context, error) { + if db.NeedLogSQL(c.Ctx) { + db.Logger.BeforeSQL(log.LogContext(*c)) + } + ctx, err := db.hooks.BeforeProcess(c) + if err != nil { + return nil, err + } + return ctx, nil +} + +func (db *DB) afterProcess(c *contexts.ContextHook) error { + err := db.hooks.AfterProcess(c) + if db.NeedLogSQL(c.Ctx) { + db.Logger.AfterSQL(log.LogContext(*c)) + } + return err +} + +func (db *DB) AddHook(h ...contexts.Hook) { + db.hooks.AddHook(h...) +} diff --git a/core/stmt.go b/core/stmt.go index ebf2af73..d46ac9c6 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -9,9 +9,8 @@ import ( "database/sql" "errors" "reflect" - "time" - "xorm.io/xorm/log" + "xorm.io/xorm/contexts" ) // Stmt reprents a stmt objects @@ -30,28 +29,16 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { i++ return "?" }) - - start := time.Now() - showSQL := db.NeedLogSQL(ctx) - if showSQL { - db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: "PREPARE", - }) - } - stmt, err := db.DB.PrepareContext(ctx, query) - if showSQL { - db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: "PREPARE", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } + hookCtx := contexts.NewContextHook(ctx, "PREPARE", nil) + ctx, err := db.beforeProcess(hookCtx) if err != nil { return nil, err } - + stmt, err := db.DB.PrepareContext(ctx, query) + hookCtx.End(ctx, nil, err) + if err := db.afterProcess(hookCtx); err != nil { + return nil, err + } return &Stmt{stmt, db, names, query}, nil } @@ -94,49 +81,28 @@ func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { } func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { - start := time.Now() - showSQL := s.db.NeedLogSQL(ctx) - if showSQL { - s.db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: s.query, - Args: args, - }) + hookCtx := contexts.NewContextHook(ctx, s.query, args) + ctx, err := s.db.beforeProcess(hookCtx) + if err != nil { + return nil, err } res, err := s.Stmt.ExecContext(ctx, args) - if showSQL { - s.db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: s.query, - Args: args, - ExecuteTime: time.Now().Sub(start), - Err: err, - }) + hookCtx.End(ctx, res, err) + if err := s.db.afterProcess(hookCtx); err != nil { + return nil, err } - return res, err + return res, nil } func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { - start := time.Now() - showSQL := s.db.NeedLogSQL(ctx) - if showSQL { - s.db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: s.query, - Args: args, - }) + hookCtx := contexts.NewContextHook(ctx, s.query, args) + ctx, err := s.db.beforeProcess(hookCtx) + if err != nil { + return nil, err } rows, err := s.Stmt.QueryContext(ctx, args...) - if showSQL { - s.db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: s.query, - Args: args, - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - if err != nil { + hookCtx.End(ctx, nil, err) + if err := s.db.afterProcess(hookCtx); err != nil { return nil, err } return &Rows{rows, s.db}, nil diff --git a/core/tx.go b/core/tx.go index 99a8097d..9b2988af 100644 --- a/core/tx.go +++ b/core/tx.go @@ -7,9 +7,8 @@ package core import ( "context" "database/sql" - "time" - "xorm.io/xorm/log" + "xorm.io/xorm/contexts" ) var ( @@ -23,24 +22,14 @@ type Tx struct { } func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { - start := time.Now() - showSQL := db.NeedLogSQL(ctx) - if showSQL { - db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: "BEGIN TRANSACTION", - }) + hookCtx := contexts.NewContextHook(ctx, "BEGIN TRANSACTION", nil) + ctx, err := db.beforeProcess(hookCtx) + if err != nil { + return nil, err } tx, err := db.DB.BeginTx(ctx, opts) - if showSQL { - db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: "BEGIN TRANSACTION", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - if err != nil { + hookCtx.End(ctx, nil, err) + if err := db.afterProcess(hookCtx); err != nil { return nil, err } return &Tx{tx, db}, nil @@ -58,25 +47,14 @@ func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { i++ return "?" }) - - start := time.Now() - showSQL := tx.db.NeedLogSQL(ctx) - if showSQL { - tx.db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: "PREPARE", - }) + hookCtx := contexts.NewContextHook(ctx, "PREPARE", nil) + ctx, err := tx.db.beforeProcess(hookCtx) + if err != nil { + return nil, err } stmt, err := tx.Tx.PrepareContext(ctx, query) - if showSQL { - tx.db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: "PREPARE", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - if err != nil { + hookCtx.End(ctx, nil, err) + if err := tx.db.afterProcess(hookCtx); err != nil { return nil, err } return &Stmt{stmt, tx.db, names, query}, nil @@ -116,24 +94,15 @@ func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{ } func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - start := time.Now() - showSQL := tx.db.NeedLogSQL(ctx) - if showSQL { - tx.db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - }) + hookCtx := contexts.NewContextHook(ctx, query, args) + ctx, err := tx.db.beforeProcess(hookCtx) + if err != nil { + return nil, err } res, err := tx.Tx.ExecContext(ctx, query, args...) - if showSQL { - tx.db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - ExecuteTime: time.Now().Sub(start), - Err: err, - }) + hookCtx.End(ctx, res, err) + if err := tx.db.afterProcess(hookCtx); err != nil { + return nil, err } return res, err } @@ -143,26 +112,14 @@ func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { } func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { - start := time.Now() - showSQL := tx.db.NeedLogSQL(ctx) - if showSQL { - tx.db.Logger.BeforeSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - }) + hookCtx := contexts.NewContextHook(ctx, query, args) + ctx, err := tx.db.beforeProcess(hookCtx) + if err != nil { + return nil, err } rows, err := tx.Tx.QueryContext(ctx, query, args...) - if showSQL { - tx.db.Logger.AfterSQL(log.LogContext{ - Ctx: ctx, - SQL: query, - Args: args, - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - if err != nil { + hookCtx.End(ctx, nil, err) + if err := tx.db.afterProcess(hookCtx); err != nil { if rows != nil { rows.Close() } diff --git a/engine.go b/engine.go index d99e15db..7399f41a 100644 --- a/engine.go +++ b/engine.go @@ -18,6 +18,7 @@ import ( "time" "xorm.io/xorm/caches" + "xorm.io/xorm/contexts" "xorm.io/xorm/core" "xorm.io/xorm/dialects" "xorm.io/xorm/internal/utils" @@ -1287,6 +1288,10 @@ func (engine *Engine) SetSchema(schema string) { engine.dialect.URI().SetSchema(schema) } +func (engine *Engine) AddHook(hook contexts.Hook) { + engine.db.AddHook(hook) +} + // Unscoped always disable struct tag "deleted" func (engine *Engine) Unscoped() *Session { session := engine.NewSession() @@ -1298,7 +1303,7 @@ func (engine *Engine) tbNameWithSchema(v string) string { return dialects.TableNameWithSchema(engine.dialect, v) } -// Context creates a session with the context +// ContextHook 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 02a57ab4..cdd9dd44 100644 --- a/engine_group.go +++ b/engine_group.go @@ -9,6 +9,7 @@ import ( "time" "xorm.io/xorm/caches" + "xorm.io/xorm/contexts" "xorm.io/xorm/dialects" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -78,7 +79,7 @@ func (eg *EngineGroup) Close() error { return nil } -// Context returned a group session +// ContextHook returned a group session func (eg *EngineGroup) Context(ctx context.Context) *Session { sess := eg.NewSession() sess.isAutoClose = true @@ -143,6 +144,13 @@ func (eg *EngineGroup) SetLogger(logger interface{}) { } } +func (eg *EngineGroup) AddHook(hook contexts.Hook) { + eg.Engine.AddHook(hook) + for i := 0; i < len(eg.slaves); i++ { + eg.slaves[i].AddHook(hook) + } +} + // SetLogLevel sets the logger level func (eg *EngineGroup) SetLogLevel(level log.LogLevel) { eg.Engine.SetLogLevel(level) diff --git a/interface.go b/interface.go index 262a2cfe..6aac4ae8 100644 --- a/interface.go +++ b/interface.go @@ -11,6 +11,7 @@ import ( "time" "xorm.io/xorm/caches" + "xorm.io/xorm/contexts" "xorm.io/xorm/dialects" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -111,6 +112,7 @@ type EngineInterface interface { SetTableMapper(names.Mapper) SetTZDatabase(tz *time.Location) SetTZLocation(tz *time.Location) + AddHook(hook contexts.Hook) ShowSQL(show ...bool) Sync(...interface{}) error Sync2(...interface{}) error diff --git a/log/logger_context.go b/log/logger_context.go index faed26d6..6b7252ef 100644 --- a/log/logger_context.go +++ b/log/logger_context.go @@ -5,19 +5,13 @@ package log import ( - "context" "fmt" - "time" + + "xorm.io/xorm/contexts" ) // LogContext represents a log context -type LogContext struct { - Ctx context.Context - SQL string // log content or SQL - Args []interface{} // if it's a SQL, it's the arguments - ExecuteTime time.Duration - Err error // SQL executed error -} +type LogContext contexts.ContextHook // SQLLogger represents an interface to log SQL type SQLLogger interface { diff --git a/session.go b/session.go index 9f47d9b4..761b1415 100644 --- a/session.go +++ b/session.go @@ -887,7 +887,7 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) { } } -// Context sets the context on this session +// ContextHook sets the context on this session func (session *Session) Context(ctx context.Context) *Session { session.ctx = ctx return session From 3bb787a2f7de4aebc276545927eef53de042ffaf Mon Sep 17 00:00:00 2001 From: daxiong Date: Thu, 23 Apr 2020 12:57:44 +0000 Subject: [PATCH 102/112] fix GetColumns missing ordinal position (#1660) fix GetColumns missing ordinal position Co-authored-by: zhubo <816078@qq.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1660 Reviewed-by: Lunny Xiao --- dialects/mysql.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dialects/mysql.go b/dialects/mysql.go index ac916c89..f9a2e943 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -308,7 +308,8 @@ func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{db.uri.DBName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + - " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + + " ORDER BY `INFORMATION_SCHEMA`.`COLUMNS`.ORDINAL_POSITION" rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { From 8ebcb8b55799f19d2d5b0968415bf1dcb87a1e96 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 26 Apr 2020 11:34:05 +0000 Subject: [PATCH 103/112] Fix find with another struct (#1666) Fix find with another struct Reviewed-on: https://gitea.com/xorm/xorm/pulls/1666 --- integrations/session_find_test.go | 7 +++++++ internal/statements/pk.go | 1 - session_find.go | 7 +++++-- tags/parser.go | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 00477235..b9d722ba 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -708,6 +708,13 @@ func TestFindExtends(t *testing.T) { err = testEngine.Find(&results) assert.NoError(t, err) assert.EqualValues(t, 2, len(results)) + + results = make([]FindExtendsA, 0, 2) + err = testEngine.Find(&results, &FindExtendsB{ + ID: 1, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) } func TestFindExtends3(t *testing.T) { diff --git a/internal/statements/pk.go b/internal/statements/pk.go index b6ae0f23..d0787719 100644 --- a/internal/statements/pk.go +++ b/internal/statements/pk.go @@ -64,7 +64,6 @@ func (statement *Statement) ProcessIDParam() error { } if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) { - fmt.Println("=====", statement.RefTable.PrimaryKeys, statement.idParam) return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d", len(statement.RefTable.PrimaryKeys), len(statement.idParam), diff --git a/session_find.go b/session_find.go index 6fbca695..3bc6a642 100644 --- a/session_find.go +++ b/session_find.go @@ -108,8 +108,11 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) ) if tp == tpStruct { if !session.statement.NoAutoCondition && len(condiBean) > 0 { - var err error - autoCond, err = session.statement.BuildConds(table, condiBean[0], true, true, false, true, addedTableName) + condTable, err := session.engine.tagParser.Parse(reflect.ValueOf(condiBean[0])) + if err != nil { + return err + } + autoCond, err = session.statement.BuildConds(condTable, condiBean[0], true, true, false, true, addedTableName) if err != nil { return err } diff --git a/tags/parser.go b/tags/parser.go index 236d2d46..add30a13 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -115,6 +115,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { t := v.Type() if t.Kind() == reflect.Ptr { t = t.Elem() + v = v.Elem() } if t.Kind() != reflect.Struct { return nil, ErrUnsupportedType From 55594d1dbeabef08fb516fcec1fc95a72d624f69 Mon Sep 17 00:00:00 2001 From: tmalaher Date: Fri, 8 May 2020 12:13:13 +0000 Subject: [PATCH 104/112] Oracle uses double quotes for quoting table/column/etc. names (#1674) Oracle uses doulbe quotes for quoting table/column/etc. names Reviewed-on: https://gitea.com/xorm/xorm/pulls/1674 Reviewed-by: Lunny Xiao --- dialects/oracle.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dialects/oracle.go b/dialects/oracle.go index 2620b0bb..91eed251 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -500,8 +500,8 @@ var ( } oracleQuoter = schemas.Quoter{ - Prefix: '[', - Suffix: ']', + Prefix: '"', + Suffix: '"', IsReserved: schemas.AlwaysReserve, } ) From d485101331f5957067fb83e227e3d5aedcf24f2d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 22 May 2020 02:53:55 +0000 Subject: [PATCH 105/112] chore: improve snakeCasedName performance (#1688) chore: update chore: udpate chore: improve snakeCasedName performance Co-authored-by: Bo-Yi Wu Reviewed-on: https://gitea.com/xorm/xorm/pulls/1688 Reviewed-by: appleboy --- names/mapper.go | 20 +++++++++++++------- names/mapper_test.go | 11 +++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/names/mapper.go b/names/mapper.go index 4aaf0844..80aa48d5 100644 --- a/names/mapper.go +++ b/names/mapper.go @@ -7,6 +7,7 @@ package names import ( "strings" "sync" + "unsafe" ) // Mapper represents a name convertation between struct's fields name and table's column name @@ -77,19 +78,24 @@ func (m SameMapper) Table2Obj(t string) string { type SnakeMapper struct { } +func b2s(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + func snakeCasedName(name string) string { - newstr := make([]rune, 0) - for idx, chr := range name { - if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { - if idx > 0 { + newstr := make([]byte, 0, len(name)+1) + for i := 0; i < len(name); i++ { + c := name[i] + if isUpper := 'A' <= c && c <= 'Z'; isUpper { + if i > 0 { newstr = append(newstr, '_') } - chr -= ('A' - 'a') + c += 'a' - 'A' } - newstr = append(newstr, chr) + newstr = append(newstr, c) } - return string(newstr) + return b2s(newstr) } func (mapper SnakeMapper) Obj2Table(name string) string { diff --git a/names/mapper_test.go b/names/mapper_test.go index 0edfd2a8..5c6dc5d9 100644 --- a/names/mapper_test.go +++ b/names/mapper_test.go @@ -5,6 +5,7 @@ package names import ( + "strings" "testing" ) @@ -47,3 +48,13 @@ func TestGonicMapperToObj(t *testing.T) { } } } + +func BenchmarkSnakeCasedName(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + s := strings.Repeat("FooBar", 32) + for i := 0; i < b.N; i++ { + _ = snakeCasedName(s) + } +} From f071e5eb9638d1ffaccf4abe8d49d2b95bcb7551 Mon Sep 17 00:00:00 2001 From: appleboy Date: Sat, 23 May 2020 02:22:08 +0000 Subject: [PATCH 106/112] chore: improve titleCasedName performance (#1691) udpate Signed-off-by: Bo-Yi Wu chore: improve titleCasedName performance Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu Reviewed-on: https://gitea.com/xorm/xorm/pulls/1691 --- names/mapper.go | 15 ++++++++------- names/mapper_test.go | 10 ++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/names/mapper.go b/names/mapper.go index 80aa48d5..79add76e 100644 --- a/names/mapper.go +++ b/names/mapper.go @@ -103,27 +103,28 @@ func (mapper SnakeMapper) Obj2Table(name string) string { } func titleCasedName(name string) string { - newstr := make([]rune, 0) + newstr := make([]byte, 0, len(name)) upNextChar := true name = strings.ToLower(name) - for _, chr := range name { + for i := 0; i < len(name); i++ { + c := name[i] switch { case upNextChar: upNextChar = false - if 'a' <= chr && chr <= 'z' { - chr -= ('a' - 'A') + if 'a' <= c && c <= 'z' { + c -= 'a' - 'A' } - case chr == '_': + case c == '_': upNextChar = true continue } - newstr = append(newstr, chr) + newstr = append(newstr, c) } - return string(newstr) + return b2s(newstr) } func (mapper SnakeMapper) Table2Obj(name string) string { diff --git a/names/mapper_test.go b/names/mapper_test.go index 5c6dc5d9..a39cb569 100644 --- a/names/mapper_test.go +++ b/names/mapper_test.go @@ -58,3 +58,13 @@ func BenchmarkSnakeCasedName(b *testing.B) { _ = snakeCasedName(s) } } + +func BenchmarkTitleCasedName(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + s := strings.Repeat("foo_bar", 32) + for i := 0; i < b.N; i++ { + _ = titleCasedName(s) + } +} From b08f962d7a7a785fb25108e4128aba6f6f23ec21 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 13 Jun 2020 01:20:25 +0000 Subject: [PATCH 107/112] Fix bug when ID used but no reference table given (#1709) Fix bug when ID used but no reference table given Reviewed-on: https://gitea.com/xorm/xorm/pulls/1709 --- integrations/session_update_test.go | 11 ++++++++++- internal/statements/pk.go | 22 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 8800246c..1bc1f32a 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "xorm.io/xorm" + "xorm.io/xorm/internal/statements" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" ) @@ -39,6 +40,14 @@ func TestUpdateMap(t *testing.T) { }) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + + cnt, err = testEngine.Table("update_table").ID(tb.Id).Update(map[string]interface{}{ + "name": "test2", + "age": 36, + }) + assert.Error(t, err) + assert.True(t, statements.IsIDConditionWithNoTableErr(err)) + assert.EqualValues(t, 0, cnt) } func TestUpdateLimit(t *testing.T) { @@ -988,7 +997,7 @@ func TestUpdateMapContent(t *testing.T) { assert.EqualValues(t, false, c2.IsMan) assert.EqualValues(t, 2, c2.Gender) - cnt, err = testEngine.Table(testEngine.TableName(new(UpdateMapContent))).ID(c.Id).Update(map[string]interface{}{ + cnt, err = testEngine.Table(new(UpdateMapContent)).ID(c.Id).Update(map[string]interface{}{ "age": 15, "is_man": true, "gender": 1, diff --git a/internal/statements/pk.go b/internal/statements/pk.go index d0787719..59da89c0 100644 --- a/internal/statements/pk.go +++ b/internal/statements/pk.go @@ -20,6 +20,21 @@ var ( uintType = reflect.TypeOf(uint64(0)) ) +// ErrIDConditionWithNoTable represents an error there is no reference table with an ID condition +type ErrIDConditionWithNoTable struct { + ID schemas.PK +} + +func (err ErrIDConditionWithNoTable) Error() string { + return fmt.Sprintf("ID condition %#v need reference table", err.ID) +} + +// IsIDConditionWithNoTableErr return true if the err is ErrIDConditionWithNoTable +func IsIDConditionWithNoTableErr(err error) bool { + _, ok := err.(ErrIDConditionWithNoTable) + return ok +} + // ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" func (statement *Statement) ID(id interface{}) *Statement { switch t := id.(type) { @@ -58,11 +73,16 @@ func (statement *Statement) ID(id interface{}) *Statement { return statement } +// ProcessIDParam handles the process of id condition func (statement *Statement) ProcessIDParam() error { - if statement.idParam == nil || statement.RefTable == nil { + if statement.idParam == nil { return nil } + if statement.RefTable == nil { + return ErrIDConditionWithNoTable{statement.idParam} + } + if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) { return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d", len(statement.RefTable.PrimaryKeys), From 4dde8f14e667c49a1c49c41ab72fbbfcc853b758 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 13 Jun 2020 05:31:33 +0000 Subject: [PATCH 108/112] Fix find and count bug (#1651) Fix bug fix mssql findandcount Fix find and count bug Reviewed-on: https://gitea.com/xorm/xorm/pulls/1651 --- engine.go | 70 ------------------------------- integrations/session_find_test.go | 42 ++++++++++++++++++- schemas/column.go | 16 +++++++ schemas/table.go | 49 ++++++++++++++++++++++ session_find.go | 13 ++++-- 5 files changed, 114 insertions(+), 76 deletions(-) diff --git a/engine.go b/engine.go index 7399f41a..b5cb6558 100644 --- a/engine.go +++ b/engine.go @@ -816,81 +816,11 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { return session.IsTableExist(beanOrTableName) } -// IDOf get id from one struct -func (engine *Engine) IDOf(bean interface{}) (schemas.PK, error) { - return engine.IDOfV(reflect.ValueOf(bean)) -} - // TableName returns table name with schema prefix if has func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string { return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...) } -// IDOfV get id from one value of struct -func (engine *Engine) IDOfV(rv reflect.Value) (schemas.PK, error) { - return engine.idOfV(rv) -} - -func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) { - v := reflect.Indirect(rv) - table, err := engine.tagParser.ParseWithCache(v) - if err != nil { - return nil, err - } - - pk := make([]interface{}, len(table.PrimaryKeys)) - for i, col := range table.PKColumns() { - var err error - - fieldName := col.FieldName - for { - parts := strings.SplitN(fieldName, ".", 2) - if len(parts) == 1 { - break - } - - v = v.FieldByName(parts[0]) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - if v.Kind() != reflect.Struct { - return nil, ErrUnSupportedType - } - fieldName = parts[1] - } - - pkField := v.FieldByName(fieldName) - switch pkField.Kind() { - case reflect.String: - pk[i], err = engine.idTypeAssertion(col, pkField.String()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10)) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - // id of uint will be converted to int64 - pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10)) - } - - if err != nil { - return nil, err - } - } - return schemas.PK(pk), nil -} - -func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) { - if col.SQLType.IsNumeric() { - n, err := strconv.ParseInt(sid, 10, 64) - if err != nil { - return nil, err - } - return n, nil - } else if col.SQLType.IsText() { - return sid, nil - } else { - return nil, errors.New("not supported") - } -} - // CreateIndexes create indexes func (engine *Engine) CreateIndexes(bean interface{}) error { session := engine.NewSession() diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index b9d722ba..95cf9384 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -502,10 +502,48 @@ func TestFindAndCountOneFunc(t *testing.T) { assert.EqualValues(t, 1, cnt) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).Limit(1).FindAndCount(&results) + cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) - assert.EqualValues(t, 1, cnt) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 1, + Content: "111", + Msg: false, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 1, + Content: "111", + Msg: false, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 2, + Content: "222", + Msg: true, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 2, + Content: "222", + Msg: true, + }, results[0]) results = make([]FindAndCountStruct, 0, 1) cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg"). diff --git a/schemas/column.go b/schemas/column.go index 418629ac..db66a3a6 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -5,8 +5,10 @@ package schemas import ( + "errors" "fmt" "reflect" + "strconv" "strings" "time" ) @@ -115,3 +117,17 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { return &fieldValue, nil } + +// ConvertID converts id content to suitable type according column type +func (col *Column) ConvertID(sid string) (interface{}, error) { + if col.SQLType.IsNumeric() { + n, err := strconv.ParseInt(sid, 10, 64) + if err != nil { + return nil, err + } + return n, nil + } else if col.SQLType.IsText() { + return sid, nil + } + return nil, errors.New("not supported") +} diff --git a/schemas/table.go b/schemas/table.go index 38596991..6c57a7e3 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -5,7 +5,9 @@ package schemas import ( + "fmt" "reflect" + "strconv" "strings" ) @@ -28,6 +30,7 @@ type Table struct { Comment string } +// NewEmptyTable creates an empty table func NewEmptyTable() *Table { return NewTable("", nil) } @@ -44,10 +47,12 @@ func NewTable(name string, t reflect.Type) *Table { } } +// Columns returns table's columns func (table *Table) Columns() []*Column { return table.columns } +// ColumnsSeq returns table's column names according sequence func (table *Table) ColumnsSeq() []string { return table.columnsSeq } @@ -61,6 +66,7 @@ func (table *Table) columnsByName(name string) []*Column { return nil } +// GetColumn returns column according column name, if column not found, return nil func (table *Table) GetColumn(name string) *Column { cols := table.columnsByName(name) if cols != nil { @@ -70,6 +76,7 @@ func (table *Table) GetColumn(name string) *Column { return nil } +// GetColumnIdx returns column according name and idx func (table *Table) GetColumnIdx(name string, idx int) *Column { cols := table.columnsByName(name) if cols != nil && idx < len(cols) { @@ -144,3 +151,45 @@ func (table *Table) AddColumn(col *Column) { func (table *Table) AddIndex(index *Index) { table.Indexes[index.Name] = index } + +// IDOfV get id from one value of struct +func (table *Table) IDOfV(rv reflect.Value) (PK, error) { + v := reflect.Indirect(rv) + pk := make([]interface{}, len(table.PrimaryKeys)) + for i, col := range table.PKColumns() { + var err error + + fieldName := col.FieldName + for { + parts := strings.SplitN(fieldName, ".", 2) + if len(parts) == 1 { + break + } + + 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()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + pk[i], err = col.ConvertID(strconv.FormatInt(pkField.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // id of uint will be converted to int64 + pk[i], err = col.ConvertID(strconv.FormatUint(pkField.Uint(), 10)) + } + + if err != nil { + return nil, err + } + } + return PK(pk), nil +} diff --git a/session_find.go b/session_find.go index 3bc6a642..642093f2 100644 --- a/session_find.go +++ b/session_find.go @@ -60,6 +60,12 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte if session.statement.OrderStr != "" { session.statement.OrderStr = "" } + if session.statement.LimitN != nil { + session.statement.LimitN = nil + } + if session.statement.Start > 0 { + session.statement.Start = 0 + } // session has stored the conditions so we use `unscoped` to avoid duplicated condition. return session.Unscoped().Count(reflect.New(sliceElementType).Interface()) @@ -320,7 +326,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { - pk[i], err = session.engine.idTypeAssertion(col, res[i]) + pk[i], err = col.ConvertID(res[i]) if err != nil { return err } @@ -370,7 +376,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } else { session.engine.logger.Debugf("[cache] cache hit bean: %v, %v, %v", tableName, id, bean) - pk, err := session.engine.IDOf(bean) + pk, err := table.IDOfV(reflect.ValueOf(bean)) if err != nil { return err } @@ -419,7 +425,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in if err != nil { return err } - session.statement = statement vs := reflect.Indirect(reflect.ValueOf(beans)) @@ -428,7 +433,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in if rv.Kind() != reflect.Ptr { rv = rv.Addr() } - id, err := session.engine.idOfV(rv) + id, err := table.IDOfV(rv) if err != nil { return err } From 08a18027a0caab739b853d3deadce464560c3672 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 13 Jun 2020 13:37:43 +0800 Subject: [PATCH 109/112] Update README badges --- README.md | 4 +--- README_CN.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a15be488..ed866224 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ Xorm is a simple and powerful ORM for Go. -[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) -[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) -[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) ## Notice diff --git a/README_CN.md b/README_CN.md index f6f88310..80245dd3 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,9 +4,7 @@ xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 -[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) -[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) -[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) ## Notice From b434b045ad863a3b250149ee543d2e600b284007 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 16 Jun 2020 02:09:37 +0000 Subject: [PATCH 110/112] Add missing changelog for 1.0.2 (#1712) Add missing changelog for 1.0.2 Reviewed-on: https://gitea.com/xorm/xorm/pulls/1712 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd081e0..22f6157a 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.2](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1261) - 2020-06-16 + +* FEATURES + * Add Hook (#1644) +* BUGFIXES + * Fix bug when ID used but no reference table given (#1709) + * Fix find and count bug (#1651) +* ENHANCEMENTS + * chore: improve snakeCasedName performance (#1688) + * Fix find with another struct (#1666) + * fix GetColumns missing ordinal position (#1660) +* MISC + * chore: improve titleCasedName performance (#1691) + ## [1.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25 * BUGFIXES From cc7d219065268513354dca1f528cdebe2152ac32 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 18 Jun 2020 11:36:19 +0000 Subject: [PATCH 111/112] Upgrade some dependencies (#1718) Upgrade some dependencies Reviewed-on: https://gitea.com/xorm/xorm/pulls/1718 --- go.mod | 10 ++--- go.sum | 132 +++++++-------------------------------------------------- 2 files changed, 20 insertions(+), 122 deletions(-) diff --git a/go.mod b/go.mod index ff42c0f7..f6a98156 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,12 @@ module xorm.io/xorm go 1.11 require ( - github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 - github.com/go-sql-driver/mysql v1.4.1 - github.com/kr/pretty v0.1.0 // indirect - github.com/lib/pq v1.0.0 - github.com/mattn/go-sqlite3 v1.10.0 + 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 v2.0.3+incompatible github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - google.golang.org/appengine v1.6.0 // indirect xorm.io/builder v0.3.7 ) diff --git a/go.sum b/go.sum index 84f9126e..2da01eeb 100644 --- a/go.sum +++ b/go.sum @@ -1,90 +1,33 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= 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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.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.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o= -github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= +github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/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/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/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/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/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/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 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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= @@ -93,60 +36,20 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/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 h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 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 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/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/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= -google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -154,8 +57,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= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= From 9b41b879a700c09250df797c12d3d046a1e09fbb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 26 Jun 2020 13:06:46 +0000 Subject: [PATCH 112/112] return the result on transaction (#1725) return the result on transaction Reviewed-on: https://gitea.com/xorm/xorm/pulls/1725 --- engine.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine.go b/engine.go index b5cb6558..d5e599d6 100644 --- a/engine.go +++ b/engine.go @@ -1263,11 +1263,11 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf result, err := f(session) if err != nil { - return nil, err + return result, err } if err := session.Commit(); err != nil { - return nil, err + return result, err } return result, nil