From 296ec09941fd0a1817b07057cd6aae3e528fa67e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 7 Aug 2019 15:19:49 +0800 Subject: [PATCH 01/18] db2 support --- dialect_db2.go | 408 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + test_db2.sh | 11 ++ xorm_db2_test.go | 11 ++ 5 files changed, 433 insertions(+) create mode 100644 dialect_db2.go create mode 100755 test_db2.sh create mode 100644 xorm_db2_test.go diff --git a/dialect_db2.go b/dialect_db2.go new file mode 100644 index 00000000..fe0e11f0 --- /dev/null +++ b/dialect_db2.go @@ -0,0 +1,408 @@ +// 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" + "strconv" + "strings" + + "xorm.io/xorm/core" +) + +type db2 struct { + core.Base +} + +func (db *db2) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { + err := db.Base.Init(d, db, uri, drivername, dataSourceName) + if err != nil { + return err + } + return nil +} + +func (db *db2) SqlType(c *core.Column) string { + var res string + switch t := c.SQLType.Name; t { + case core.TinyInt: + res = core.SmallInt + return res + case core.Bit: + res = core.Boolean + return res + case core.MediumInt, core.Int, core.Integer: + if c.IsAutoIncrement { + return core.Serial + } + return core.Integer + case core.BigInt: + if c.IsAutoIncrement { + return core.BigSerial + } + return core.BigInt + case core.Serial, core.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: + 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: + return "DOUBLE PRECISION" + default: + if c.IsAutoIncrement { + return core.Serial + } + res = t + } + + if strings.EqualFold(res, "bool") { + // for bool, we don't need length information + return res + } + hasLen1 := (c.Length > 0) + hasLen2 := (c.Length2 > 0) + + if hasLen2 { + res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + } else if hasLen1 { + res += "(" + strconv.Itoa(c.Length) + ")" + } + return res +} + +func (db *db2) SupportInsertMany() bool { + return true +} + +func (db *db2) IsReserved(name string) bool { + _, ok := postgresReservedWords[name] + return ok +} + +func (db *db2) Quote(name string) string { + name = strings.Replace(name, ".", `"."`, -1) + return "\"" + name + "\"" +} + +func (db *db2) AutoIncrStr() string { + return "" +} + +func (db *db2) SupportEngine() bool { + return false +} + +func (db *db2) SupportCharset() bool { + return false +} + +func (db *db2) IndexOnTable() bool { + return false +} + +func (db *db2) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + if len(db.Schema) == 0 { + args := []interface{}{tableName, idxName} + return `SELECT indexname FROM pg_indexes WHERE tablename = ? AND indexname = ?`, args + } + + args := []interface{}{db.Schema, tableName, idxName} + return `SELECT indexname FROM pg_indexes ` + + `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args +} + +func (db *db2) TableCheckSql(tableName string) (string, []interface{}) { + if len(db.Schema) == 0 { + args := []interface{}{tableName} + return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args + } + + args := []interface{}{db.Schema, tableName} + return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args +} + +func (db *db2) ModifyColumnSql(tableName string, col *core.Column) string { + if len(db.Schema) == 0 { + 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.Schema, tableName, col.Name, db.SqlType(col)) +} + +func (db *db2) DropIndexSql(tableName string, index *core.Index) string { + quote := db.Quote + idxName := index.Name + + tableName = strings.Replace(tableName, `"`, "", -1) + tableName = strings.Replace(tableName, `.`, "_", -1) + + if !strings.HasPrefix(idxName, "UQE_") && + !strings.HasPrefix(idxName, "IDX_") { + if index.Type == core.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 + } + return fmt.Sprintf("DROP INDEX %v", quote(idxName)) +} + +func (db *db2) IsColumnExist(tableName, colName string) (bool, error) { + args := []interface{}{db.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 { + args = []interface{}{tableName, colName} + query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + + " AND column_name = $2" + } + db.LogSQL(query, args) + + rows, err := db.DB().Query(query, args...) + if err != nil { + return false, err + } + defer rows.Close() + + return rows.Next(), nil +} + +func (db *db2) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { + args := []interface{}{tableName} + s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix , + CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, + CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey +FROM pg_attribute f + JOIN pg_class c ON c.oid = f.attrelid JOIN pg_type t ON t.oid = f.atttypid + LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey) + LEFT JOIN pg_class AS g ON p.confrelid = g.oid + 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.Schema) != 0 { + args = append(args, db.Schema) + f = " AND s.table_schema = $2" + } + s = fmt.Sprintf(s, f) + + db.LogSQL(s, args) + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + cols := make(map[string]*core.Column) + colSeq := make([]string, 0) + + for rows.Next() { + col := new(core.Column) + col.Indexes = make(map[string]int) + + var colName, isNullable, dataType string + var maxLenStr, colDefault, numPrecision, numRadix *string + var isPK, isUnique bool + err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &numPrecision, &numRadix, &isPK, &isUnique) + if err != nil { + return nil, nil, err + } + + // fmt.Println(args, colName, isNullable, dataType, maxLenStr, colDefault, numPrecision, numRadix, isPK, isUnique) + var maxLen int + if maxLenStr != nil { + maxLen, err = strconv.Atoi(*maxLenStr) + if err != nil { + return nil, nil, err + } + } + + col.Name = strings.Trim(colName, `" `) + + if colDefault != nil || isPK { + if isPK { + col.IsPrimaryKey = true + } else { + col.Default = *colDefault + } + } + + if colDefault != nil && strings.HasPrefix(*colDefault, "nextval(") { + col.IsAutoIncrement = true + } + + col.Nullable = (isNullable == "YES") + + switch dataType { + case "character varying", "character": + col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 0, DefaultLength2: 0} + case "timestamp without time zone": + col.SQLType = core.SQLType{Name: core.DateTime, DefaultLength: 0, DefaultLength2: 0} + case "timestamp with time zone": + col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + case "double precision": + col.SQLType = core.SQLType{Name: core.Double, DefaultLength: 0, DefaultLength2: 0} + case "boolean": + col.SQLType = core.SQLType{Name: core.Bool, DefaultLength: 0, DefaultLength2: 0} + case "time without time zone": + col.SQLType = core.SQLType{Name: core.Time, DefaultLength: 0, DefaultLength2: 0} + case "oid": + col.SQLType = core.SQLType{Name: core.BigInt, DefaultLength: 0, DefaultLength2: 0} + default: + col.SQLType = core.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} + } + if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, fmt.Errorf("Unknown colType: %v", dataType) + } + + col.Length = maxLen + + if col.SQLType.IsText() || col.SQLType.IsTime() { + if col.Default != "" { + col.Default = "'" + col.Default + "'" + } else { + if col.DefaultIsEmpty { + col.Default = "''" + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + + return colSeq, cols, nil +} + +func (db *db2) GetTables() ([]*core.Table, error) { + args := []interface{}{} + s := "SELECT tablename FROM pg_tables" + if len(db.Schema) != 0 { + args = append(args, db.Schema) + s = s + " WHERE schemaname = $1" + } + + db.LogSQL(s, args) + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + tables := make([]*core.Table, 0) + for rows.Next() { + table := core.NewEmptyTable() + var name string + err = rows.Scan(&name) + if err != nil { + return nil, err + } + table.Name = name + tables = append(tables, table) + } + return tables, nil +} + +func (db *db2) GetIndexes(tableName string) (map[string]*core.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) + s = s + " AND schemaname=$2" + } + db.LogSQL(s, args) + + rows, err := db.DB().Query(s, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + indexes := make(map[string]*core.Index, 0) + for rows.Next() { + var indexType int + var indexName, indexdef string + var colNames []string + err = rows.Scan(&indexName, &indexdef) + if err != nil { + return nil, err + } + indexName = strings.Trim(indexName, `" `) + if strings.HasSuffix(indexName, "_pkey") { + continue + } + if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") { + indexType = core.UniqueType + } else { + indexType = core.IndexType + } + colNames = getIndexColName(indexdef) + var isRegular bool + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + newIdxName := indexName[5+len(tableName):] + isRegular = true + if newIdxName != "" { + indexName = newIdxName + } + } + + index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} + for _, colName := range colNames { + index.Cols = append(index.Cols, strings.Trim(colName, `" `)) + } + index.IsRegular = isRegular + indexes[index.Name] = index + } + return indexes, nil +} + +func (db *db2) Filters() []core.Filter { + return []core.Filter{&core.QuoteFilter{}} +} + +type db2Driver struct{} + +func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + var dbName string + + kv := strings.Split(dataSourceName, ";") + for _, c := range kv { + vv := strings.Split(strings.TrimSpace(c), "=") + if len(vv) == 2 { + switch strings.ToLower(vv[0]) { + case "database": + dbName = vv[1] + } + } + } + + if dbName == "" { + return nil, errors.New("no db name provided") + } + return &core.Uri{DbName: dbName, DbType: core.MSSQL}, nil +} diff --git a/go.mod b/go.mod index d645011c..7e2bbc2f 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.7.4 github.com/jackc/pgx/v4 v4.12.0 + github.com/ibmdb/go_ibm_db v0.1.0 github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.10.2 github.com/mattn/go-sqlite3 v1.14.8 diff --git a/go.sum b/go.sum index e8024945..58754e7d 100644 --- a/go.sum +++ b/go.sum @@ -203,6 +203,8 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/ibmdb/go_ibm_db v0.1.0 h1:Ok7W7wysBUa8eyVYxWLS5vIA0VomTsurK57l5Rah1M8= +github.com/ibmdb/go_ibm_db v0.1.0/go.mod h1:nl5aUh1IzBVExcqYXaZLApaq8RUvTEph3VP49UTmEvg= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= diff --git a/test_db2.sh b/test_db2.sh new file mode 100755 index 00000000..012b7140 --- /dev/null +++ b/test_db2.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +go get github.com/ibmdb/go_ibm_db +export DB2HOME=$GOPATH/src/github.com/ibmdb/go_ibm_db/installer +cur="$PWD" +cd $DB2HOME && go run setup.go +export CGO_CFLAGS=-I$DB2HOME/include +export CGO_LDFLAGS=-L$DB2HOME/lib +export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$DB2HOME/clidriver/lib +cd $cur +go test -db=go_ibm_db -tags=db2 -conn_str="HOSTNAME=localhost;DATABASE=testdb;PORT=50000;UID=db2inst1;PWD=password" \ No newline at end of file diff --git a/xorm_db2_test.go b/xorm_db2_test.go new file mode 100644 index 00000000..cb0cea5d --- /dev/null +++ b/xorm_db2_test.go @@ -0,0 +1,11 @@ +// 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. + +// +build db2 + +package xorm + +import ( + _ "github.com/ibmdb/go_ibm_db" +) -- 2.40.1 From 817dbe4f61c23612de8852a1c1c9f3fdb557b859 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 18 Nov 2019 09:39:01 +0800 Subject: [PATCH 02/18] fix db2 gettables --- dialect_db2.go | 87 ++++++++++++++++++++++++++------------------------ test_db2.sh | 2 +- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/dialect_db2.go b/dialect_db2.go index fe0e11f0..4712f559 100644 --- a/dialect_db2.go +++ b/dialect_db2.go @@ -191,24 +191,28 @@ func (db *db2) IsColumnExist(tableName, colName string) (bool, error) { func (db *db2) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { args := []interface{}{tableName} - s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix , - CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, - CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey -FROM pg_attribute f - JOIN pg_class c ON c.oid = f.attrelid JOIN pg_type t ON t.oid = f.atttypid - LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum - LEFT JOIN pg_namespace n ON n.oid = c.relnamespace - LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey) - LEFT JOIN pg_class AS g ON p.confrelid = g.oid - 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;` + s := `Select c.colname as column_name, + c.colno as position, + c.typename as data_type, + c.length, + c.scale, + c.remarks as description, + case when c.nulls = 'Y' then 1 else 0 end as nullable, + default as default_value, + case when c.identity ='Y' then 1 else 0 end as is_identity, + case when c.generated ='' then 0 else 1 end as is_computed, + c.text as computed_formula +from syscat.columns c +inner join syscat.tables t on + t.tabschema = c.tabschema and t.tabname = c.tabname +where t.type = 'T' AND c.tabname = ?` var f string if len(db.Schema) != 0 { args = append(args, db.Schema) - f = " AND s.table_schema = $2" + f = " AND c.tabschema = ?" } - s = fmt.Sprintf(s, f) + s = s + f db.LogSQL(s, args) @@ -225,15 +229,15 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att col := new(core.Column) col.Indexes = make(map[string]int) - var colName, isNullable, dataType string - var maxLenStr, colDefault, numPrecision, numRadix *string - var isPK, isUnique bool - err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &numPrecision, &numRadix, &isPK, &isUnique) + var colName, position, dataType, numericScale string + var description, colDefault, computedFormula, maxLenStr *string + var isComputed bool + err = rows.Scan(&colName, &position, &dataType, &maxLenStr, &numericScale, &description, &col.Nullable, &colDefault, &col.IsPrimaryKey, &isComputed, &computedFormula) if err != nil { return nil, nil, err } - // fmt.Println(args, colName, isNullable, dataType, maxLenStr, colDefault, numPrecision, numRadix, isPK, isUnique) + //fmt.Println(colName, position, dataType, maxLenStr, numericScale, description, col.Nullable, colDefault, col.IsPrimaryKey, isComputed, computedFormula) var maxLen int if maxLenStr != nil { maxLen, err = strconv.Atoi(*maxLenStr) @@ -243,24 +247,18 @@ 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 || isPK { - if isPK { - col.IsPrimaryKey = true - } else { - col.Default = *colDefault - } + if colDefault != nil { + col.DefaultIsEmpty = false + col.Default = *colDefault } if colDefault != nil && strings.HasPrefix(*colDefault, "nextval(") { col.IsAutoIncrement = true } - col.Nullable = (isNullable == "YES") - switch dataType { - case "character varying", "character": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 0, DefaultLength2: 0} + case "character", "CHARACTER": + col.SQLType = core.SQLType{Name: core.Char, DefaultLength: 0, DefaultLength2: 0} case "timestamp without time zone": col.SQLType = core.SQLType{Name: core.DateTime, DefaultLength: 0, DefaultLength2: 0} case "timestamp with time zone": @@ -300,10 +298,10 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att func (db *db2) GetTables() ([]*core.Table, error) { args := []interface{}{} - s := "SELECT tablename FROM pg_tables" + s := "SELECT NAME FROM SYSIBM.SYSTABLES WHERE type = 'T'" if len(db.Schema) != 0 { args = append(args, db.Schema) - s = s + " WHERE schemaname = $1" + s = s + " AND creator = ?" } db.LogSQL(s, args) @@ -330,10 +328,13 @@ func (db *db2) GetTables() ([]*core.Table, error) { func (db *db2) GetIndexes(tableName string) (map[string]*core.Index, error) { args := []interface{}{tableName} - s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1") + s := fmt.Sprintf(`select uniquerule, + indname as index_name, + replace(substring(colnames,2,length(colnames)),'+',',') as columns +from syscat.indexes WHERE tabname = ?`) if len(db.Schema) != 0 { args = append(args, db.Schema) - s = s + " AND schemaname=$2" + s = s + " AND tabschema=?" } db.LogSQL(s, args) @@ -345,10 +346,11 @@ func (db *db2) GetIndexes(tableName string) (map[string]*core.Index, error) { indexes := make(map[string]*core.Index, 0) for rows.Next() { - var indexType int - var indexName, indexdef string - var colNames []string - err = rows.Scan(&indexName, &indexdef) + var indexTypeName, indexName, columns string + /*when 'P' then 'Primary key' + when 'U' then 'Unique' + when 'D' then 'Nonunique'*/ + err = rows.Scan(&indexTypeName, &indexName, &columns) if err != nil { return nil, err } @@ -356,12 +358,12 @@ func (db *db2) GetIndexes(tableName string) (map[string]*core.Index, error) { if strings.HasSuffix(indexName, "_pkey") { continue } - if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") { + var indexType int + if strings.EqualFold(indexTypeName, "U") { indexType = core.UniqueType - } else { + } else if strings.EqualFold(indexTypeName, "D") { indexType = core.IndexType } - colNames = getIndexColName(indexdef) var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { newIdxName := indexName[5+len(tableName):] @@ -372,6 +374,7 @@ func (db *db2) GetIndexes(tableName string) (map[string]*core.Index, error) { } index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} + colNames := strings.Split(columns, ",") for _, colName := range colNames { index.Cols = append(index.Cols, strings.Trim(colName, `" `)) } @@ -392,7 +395,7 @@ func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) kv := strings.Split(dataSourceName, ";") for _, c := range kv { - vv := strings.Split(strings.TrimSpace(c), "=") + vv := strings.SplitN(strings.TrimSpace(c), "=", 2) if len(vv) == 2 { switch strings.ToLower(vv[0]) { case "database": @@ -404,5 +407,5 @@ func (p *db2Driver) 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 &core.Uri{DbName: dbName, DbType: "db2"}, nil } diff --git a/test_db2.sh b/test_db2.sh index 012b7140..55f7a0de 100755 --- a/test_db2.sh +++ b/test_db2.sh @@ -8,4 +8,4 @@ export CGO_CFLAGS=-I$DB2HOME/include export CGO_LDFLAGS=-L$DB2HOME/lib export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$DB2HOME/clidriver/lib cd $cur -go test -db=go_ibm_db -tags=db2 -conn_str="HOSTNAME=localhost;DATABASE=testdb;PORT=50000;UID=db2inst1;PWD=password" \ No newline at end of file +go test -db=go_ibm_db -tags=db2 -conn_str="HOSTNAME=localhost;DATABASE=testdb;PORT=50000;UID=db2inst1;PWD=123#2@23" \ No newline at end of file -- 2.40.1 From db6c12b3d0560123e96bbcba6a891a6ee2f8c4c0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Dec 2019 22:36:21 +0800 Subject: [PATCH 03/18] fix some bugs --- dialect_db2.go | 65 ++++++++++++++++++++++++-------------- integrations/cache_test.go | 8 ++--- session_schema.go | 4 +++ 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/dialect_db2.go b/dialect_db2.go index 4712f559..68a44242 100644 --- a/dialect_db2.go +++ b/dialect_db2.go @@ -34,28 +34,12 @@ func (db *db2) SqlType(c *core.Column) string { case core.Bit: res = core.Boolean return res - case core.MediumInt, core.Int, core.Integer: - if c.IsAutoIncrement { - return core.Serial - } - return core.Integer - case core.BigInt: - if c.IsAutoIncrement { - return core.BigSerial - } - return core.BigInt - case core.Serial, core.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: return "timestamp with time zone" - case core.Float: - res = core.Real case core.TinyText, core.MediumText, core.LongText: res = core.Text case core.NVarchar: @@ -64,12 +48,7 @@ func (db *db2) SqlType(c *core.Column) string { return core.Uuid case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: return core.Bytea - case core.Double: - return "DOUBLE PRECISION" default: - if c.IsAutoIncrement { - return core.Serial - } res = t } @@ -118,6 +97,37 @@ func (db *db2) IndexOnTable() bool { return false } +func (db *db2) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { + var sql string + sql = "CREATE TABLE " + if tableName == "" { + tableName = table.Name + } + + sql += db.Quote(tableName) + " (" + + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + sql += col.StringNoPk(db) + if col.IsAutoIncrement { + sql += " GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1 )" + } + sql = strings.TrimSpace(sql) + sql += ", " + } + + if len(pkList) > 0 { + sql += "PRIMARY KEY ( " + sql += db.Quote(strings.Join(pkList, db.Quote(","))) + sql += " ), " + } + + sql = sql[:len(sql)-2] + ")" + return sql +} + func (db *db2) IndexCheckSql(tableName, idxName string) (string, []interface{}) { if len(db.Schema) == 0 { args := []interface{}{tableName, idxName} @@ -298,10 +308,10 @@ where t.type = 'T' AND c.tabname = ?` func (db *db2) GetTables() ([]*core.Table, error) { args := []interface{}{} - s := "SELECT NAME FROM SYSIBM.SYSTABLES WHERE type = 'T'" + s := "SELECT TABNAME FROM SYSCAT.TABLES WHERE type = 'T' AND OWNERTYPE = 'U'" if len(db.Schema) != 0 { args = append(args, db.Schema) - s = s + " AND creator = ?" + s = s + " AND TABSCHEMA = ?" } db.LogSQL(s, args) @@ -392,6 +402,7 @@ type db2Driver struct{} func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { var dbName string + var defaultSchema string kv := strings.Split(dataSourceName, ";") for _, c := range kv { @@ -400,6 +411,8 @@ func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) switch strings.ToLower(vv[0]) { case "database": dbName = vv[1] + case "uid": + defaultSchema = vv[1] } } } @@ -407,5 +420,9 @@ func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) if dbName == "" { return nil, errors.New("no db name provided") } - return &core.Uri{DbName: dbName, DbType: "db2"}, nil + return &core.Uri{ + DbName: dbName, + DbType: "db2", + Schema: defaultSchema, + }, nil } diff --git a/integrations/cache_test.go b/integrations/cache_test.go index 44e817b1..71a37c1f 100644 --- a/integrations/cache_test.go +++ b/integrations/cache_test.go @@ -62,7 +62,7 @@ func TestCacheFind(t *testing.T) { } boxes = make([]MailBox, 0, 2) - assert.NoError(t, testEngine.Alias("a").Where("a.id > -1").Asc("a.id").Find(&boxes)) + assert.NoError(t, testEngine.Alias("a").Where("`a`.`id` > -1").Asc("a.id").Find(&boxes)) assert.EqualValues(t, 2, len(boxes)) for i, box := range boxes { assert.Equal(t, inserts[i].Id, box.Id) @@ -77,7 +77,7 @@ func TestCacheFind(t *testing.T) { } boxes2 := make([]MailBox4, 0, 2) - assert.NoError(t, testEngine.Table("mail_box").Where("mail_box.id > -1").Asc("mail_box.id").Find(&boxes2)) + assert.NoError(t, testEngine.Table("mail_box").Where("`mail_box`.`id` > -1").Asc("mail_box.id").Find(&boxes2)) assert.EqualValues(t, 2, len(boxes2)) for i, box := range boxes2 { assert.Equal(t, inserts[i].Id, box.Id) @@ -164,14 +164,14 @@ func TestCacheGet(t *testing.T) { assert.NoError(t, err) var box1 MailBox3 - has, err := testEngine.Where("id = ?", inserts[0].Id).Get(&box1) + has, err := testEngine.Where("`id` = ?", inserts[0].Id).Get(&box1) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, "user1", box1.Username) assert.EqualValues(t, "pass1", box1.Password) var box2 MailBox3 - has, err = testEngine.Where("id = ?", inserts[0].Id).Get(&box2) + has, err = testEngine.Where("`id` = ?", inserts[0].Id).Get(&box2) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, "user1", box2.Username) diff --git a/session_schema.go b/session_schema.go index 2e64350f..7055e910 100644 --- a/session_schema.go +++ b/session_schema.go @@ -235,6 +235,7 @@ func (session *Session) Sync2(beans ...interface{}) error { tables, err := engine.dialect.GetTables(session.getQueryer(), session.ctx) if err != nil { + fmt.Println("------", tables, err) return err } @@ -244,6 +245,8 @@ func (session *Session) Sync2(beans ...interface{}) error { session.resetStatement() }() + fmt.Println("-----", tables, len(tables), len(beans)) + for _, bean := range beans { v := utils.ReflectValue(bean) table, err := engine.tagParser.ParseWithCache(v) @@ -260,6 +263,7 @@ func (session *Session) Sync2(beans ...interface{}) error { var oriTable *schemas.Table for _, tb := range tables { + fmt.Println("----", tb.Name, engine.tbNameWithSchema(tb.Name), "===", tbName, engine.tbNameWithSchema(tbName)) if strings.EqualFold(engine.tbNameWithSchema(tb.Name), engine.tbNameWithSchema(tbName)) { oriTable = tb break -- 2.40.1 From 19646ffec17154f3b0843fae9540ff18c09488ff Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 21 Feb 2020 10:16:28 +0800 Subject: [PATCH 04/18] Add tests --- Makefile | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Makefile b/Makefile index e986082e..b66d9958 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,12 @@ TEST_COCKROACH_DBNAME ?= xorm_test TEST_COCKROACH_USERNAME ?= postgres TEST_COCKROACH_PASSWORD ?= +TEST_DB2_HOST ?= db2 +TEST_DB2_PORT ?= 50000 +TEST_DB2_DBNAME ?= gitea +TEST_DB2_USERNAME ?= sa +TEST_DB2_PASSWORD ?= MwantsaSecurePassword1 + TEST_MSSQL_HOST ?= mssql:1433 TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_USERNAME ?= sa @@ -46,6 +52,9 @@ TEST_TIDB_PASSWORD ?= TEST_CACHE_ENABLE ?= false TEST_QUOTE_POLICY ?= always +DB2HOME := $(GOPATH)/src/github.com/ibmdb/go_ibm_db/installer +DB2_DRIVER_DIR := $(DB2HOME)/clidriver + .PHONY: all all: build @@ -146,6 +155,28 @@ test-cockroach\#%: go-check -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 +$(DB2_DRIVER_DIR): + GO111MODULE=off go get github.com/ibmdb/go_ibm_db + GO111MODULE=off cd $(DB2HOME) && go run setup.go + +.PNONY: test-db2 +test-db2: go-check $(DB2_DRIVER_DIR) + CGO_CFLAGS=-I$(DB2HOME)/clidriver/include \ + CGO_LDFLAGS=-L$(DB2HOME)/clidriver/lib \ + DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2HOME)/clidriver/lib \ + $(GO) test -race -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="HOSTNAME=$(TEST_DB2_HOST);DATABASE=$(TEST_DB2_DBNAME);PORT=$(TEST_DB2_PORT);UID=$(TEST_DB2_USERNAME);PWD=$(TEST_DB2_PASSWORD)" \ + -coverprofile=db2.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PNONY: test-db2\#% +test-db2\#%: go-check + CGO_CFLAGS=-I$(DB2HOME)/clidriver/include \ + CGO_LDFLAGS=-L$(DB2HOME)/clidriver/lib \ + DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2HOME)/clidriver/lib \ + $(GO) test -race -run $* -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ + -conn_str="HOSTNAME=$(TEST_DB2_HOST);DATABASE=$(TEST_DB2_DBNAME);PORT=$(TEST_DB2_PORT);UID=$(TEST_DB2_USERNAME);PWD=$(TEST_DB2_PASSWORD)" \ + -coverprofile=db2.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + .PNONY: test-mssql test-mssql: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -- 2.40.1 From 922be56e321f227607124afdc2c824d3f11c56e0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 25 Feb 2020 13:30:15 +0800 Subject: [PATCH 05/18] Use new dialect interface --- dialect_db2.go => dialects/db2.go | 162 +++++++++++++++--------------- dialects/dialect.go | 14 +++ 2 files changed, 97 insertions(+), 79 deletions(-) rename dialect_db2.go => dialects/db2.go (68%) diff --git a/dialect_db2.go b/dialects/db2.go similarity index 68% rename from dialect_db2.go rename to dialects/db2.go index 68a44242..8f146100 100644 --- a/dialect_db2.go +++ b/dialects/db2.go @@ -1,8 +1,8 @@ -// Copyright 2015 The Xorm Authors. All rights reserved. +// 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 +package dialects import ( "errors" @@ -11,13 +11,18 @@ import ( "strings" "xorm.io/xorm/core" + "xorm.io/xorm/schemas" +) + +var ( + db2ReservedWords = map[string]bool{} ) type db2 struct { - core.Base + Base } -func (db *db2) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *db2) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { err := db.Base.Init(d, db, uri, drivername, dataSourceName) if err != nil { return err @@ -25,29 +30,29 @@ func (db *db2) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string return nil } -func (db *db2) SqlType(c *core.Column) string { +func (db *db2) 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.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.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 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 default: res = t } @@ -72,13 +77,12 @@ func (db *db2) SupportInsertMany() bool { } func (db *db2) IsReserved(name string) bool { - _, ok := postgresReservedWords[name] + _, ok := db2ReservedWords[name] return ok } -func (db *db2) Quote(name string) string { - name = strings.Replace(name, ".", `"."`, -1) - return "\"" + name + "\"" +func (db *db2) Quoter() schemas.Quoter { + return schemas.Quoter{"\"", "\""} } func (db *db2) AutoIncrStr() string { @@ -97,20 +101,20 @@ func (db *db2) IndexOnTable() bool { return false } -func (db *db2) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *db2) CreateTableSql(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE " if tableName == "" { tableName = table.Name } - sql += db.Quote(tableName) + " (" + sql += db.Quoter().Quote(tableName) + " (" pkList := table.PrimaryKeys for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) if col.IsAutoIncrement { sql += " GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1 )" } @@ -120,7 +124,7 @@ func (db *db2) CreateTableSql(table *core.Table, tableName, storeEngine, charset if len(pkList) > 0 { sql += "PRIMARY KEY ( " - sql += db.Quote(strings.Join(pkList, db.Quote(","))) + sql += db.Quoter().Join(pkList, ",") sql += " ), " } @@ -128,38 +132,38 @@ func (db *db2) CreateTableSql(table *core.Table, tableName, storeEngine, charset return sql } -func (db *db2) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - if len(db.Schema) == 0 { +func (db *db2) 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 *db2) TableCheckSql(tableName string) (string, []interface{}) { - if len(db.Schema) == 0 { +func (db *db2) 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 *db2) ModifyColumnSql(tableName string, col *core.Column) string { - if len(db.Schema) == 0 { +func (db *db2) ModifyColumnSQL(tableName string, col *schemas.Column) string { + if len(db.uri.Schema) == 0 { 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 *db2) DropIndexSql(tableName string, index *core.Index) string { - quote := db.Quote +func (db *db2) DropIndexSQL(tableName string, index *schemas.Index) string { + quote := db.Quoter().Quote idxName := index.Name tableName = strings.Replace(tableName, `"`, "", -1) @@ -167,23 +171,23 @@ func (db *db2) 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 *db2) 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" @@ -199,7 +203,7 @@ func (db *db2) IsColumnExist(tableName, colName string) (bool, error) { return rows.Next(), nil } -func (db *db2) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *db2) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := `Select c.colname as column_name, c.colno as position, @@ -218,8 +222,8 @@ inner join syscat.tables t on where t.type = 'T' AND c.tabname = ?` 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 c.tabschema = ?" } s = s + f @@ -232,11 +236,11 @@ where t.type = 'T' AND c.tabname = ?` } 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, position, dataType, numericScale string @@ -268,23 +272,23 @@ where t.type = 'T' AND c.tabname = ?` switch dataType { case "character", "CHARACTER": - col.SQLType = core.SQLType{Name: core.Char, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Char, 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) } @@ -306,11 +310,11 @@ where t.type = 'T' AND c.tabname = ?` return colSeq, cols, nil } -func (db *db2) GetTables() ([]*core.Table, error) { +func (db *db2) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT TABNAME FROM SYSCAT.TABLES WHERE type = 'T' AND OWNERTYPE = 'U'" - 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 TABSCHEMA = ?" } @@ -322,9 +326,9 @@ func (db *db2) 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 { @@ -336,14 +340,14 @@ func (db *db2) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *db2) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *db2) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := fmt.Sprintf(`select uniquerule, indname as index_name, replace(substring(colnames,2,length(colnames)),'+',',') as columns from syscat.indexes WHERE tabname = ?`) - 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 tabschema=?" } db.LogSQL(s, args) @@ -354,7 +358,7 @@ from syscat.indexes WHERE tabname = ?`) } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexTypeName, indexName, columns string /*when 'P' then 'Primary key' @@ -370,9 +374,9 @@ from syscat.indexes WHERE tabname = ?`) } var indexType int if strings.EqualFold(indexTypeName, "U") { - indexType = core.UniqueType + indexType = schemas.UniqueType } else if strings.EqualFold(indexTypeName, "D") { - indexType = core.IndexType + indexType = schemas.IndexType } var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { @@ -383,7 +387,7 @@ from syscat.indexes WHERE tabname = ?`) } } - index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} + index := &schemas.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} colNames := strings.Split(columns, ",") for _, colName := range colNames { index.Cols = append(index.Cols, strings.Trim(colName, `" `)) @@ -394,13 +398,13 @@ from syscat.indexes WHERE tabname = ?`) return indexes, nil } -func (db *db2) Filters() []core.Filter { - return []core.Filter{&core.QuoteFilter{}} +func (db *db2) Filters() []Filter { + return []Filter{&QuoteFilter{}} } type db2Driver struct{} -func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *db2Driver) Parse(driverName, dataSourceName string) (*URI, error) { var dbName string var defaultSchema string @@ -420,9 +424,9 @@ func (p *db2Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) if dbName == "" { return nil, errors.New("no db name provided") } - return &core.Uri{ - DbName: dbName, - DbType: "db2", + return &URI{ + DBName: dbName, + DBType: "db2", Schema: defaultSchema, }, nil } diff --git a/dialects/dialect.go b/dialects/dialect.go index fc11eac1..d844aed9 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -211,6 +211,7 @@ func regDrvsNDialects() bool { getDriver func() Driver getDialect func() Dialect }{ +<<<<<<< HEAD "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{} }}, @@ -221,6 +222,19 @@ func regDrvsNDialects() bool { "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, +======= + "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{} }}, + "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, + "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, + "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, + "go_ibm_db": {"db2", func() Driver { return &db2Driver{} }, func() Dialect { return &db2{} }}, +>>>>>>> 538a3b2 (Use new dialect interface) } for driverName, v := range providedDrvsNDialects { -- 2.40.1 From e7df46bb606fe3acca9f38858965ce05ab7491bd Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 11 Sep 2020 15:09:05 +0800 Subject: [PATCH 06/18] Rebase codes --- dialects/db2.go | 78 ++++++++++++++++++++++++++++--------------------- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/dialects/db2.go b/dialects/db2.go index 8f146100..1b2f0038 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -5,6 +5,7 @@ package dialects import ( + "context" "errors" "fmt" "strconv" @@ -16,18 +17,20 @@ import ( var ( db2ReservedWords = map[string]bool{} + db2Quoter = schemas.Quoter{ + Prefix: '"', + Suffix: '"', + IsReserved: schemas.AlwaysReserve, + } ) type db2 struct { Base } -func (db *db2) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { - err := db.Base.Init(d, db, uri, drivername, dataSourceName) - if err != nil { - return err - } - return nil +func (db *db2) Init(uri *URI) error { + db.quoter = db2Quoter + return db.Base.Init(db, uri) } func (db *db2) SQLType(c *schemas.Column) string { @@ -81,10 +84,6 @@ func (db *db2) IsReserved(name string) bool { return ok } -func (db *db2) Quoter() schemas.Quoter { - return schemas.Quoter{"\"", "\""} -} - func (db *db2) AutoIncrStr() string { return "" } @@ -101,7 +100,7 @@ func (db *db2) IndexOnTable() bool { return false } -func (db *db2) CreateTableSql(table *schemas.Table, tableName, storeEngine, charset string) string { +func (db *db2) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { var sql string sql = "CREATE TABLE " if tableName == "" { @@ -114,7 +113,8 @@ func (db *db2) CreateTableSql(table *schemas.Table, tableName, storeEngine, char for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - sql += StringNoPk(db, col) + s, _ := ColumnString(db, col, false) + sql += s if col.IsAutoIncrement { sql += " GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1 )" } @@ -129,7 +129,7 @@ func (db *db2) CreateTableSql(table *schemas.Table, tableName, storeEngine, char } sql = sql[:len(sql)-2] + ")" - return sql + return []string{sql}, false } func (db *db2) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { @@ -143,14 +143,30 @@ func (db *db2) IndexCheckSQL(tableName, idxName string) (string, []interface{}) `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } -func (db *db2) TableCheckSQL(tableName string) (string, []interface{}) { - if len(db.uri.Schema) == 0 { - args := []interface{}{tableName} - return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args +func (db *db2) 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 } +} - args := []interface{}{db.uri.Schema, tableName} - return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args +func (db *db2) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { + if len(db.uri.Schema) == 0 { + return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE tablename = ?`, tableName) + } + return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, + db.uri.Schema, tableName, + ) } func (db *db2) ModifyColumnSQL(tableName string, col *schemas.Column) string { @@ -183,7 +199,7 @@ func (db *db2) DropIndexSQL(tableName string, index *schemas.Index) string { return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } -func (db *db2) IsColumnExist(tableName, colName string) (bool, error) { +func (db *db2) 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" @@ -192,9 +208,8 @@ func (db *db2) IsColumnExist(tableName, colName string) (bool, error) { query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + " AND column_name = $2" } - db.LogSQL(query, args) - rows, err := db.DB().Query(query, args...) + rows, err := queryer.QueryContext(ctx, query, args...) if err != nil { return false, err } @@ -203,7 +218,7 @@ func (db *db2) IsColumnExist(tableName, colName string) (bool, error) { return rows.Next(), nil } -func (db *db2) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { +func (db *db2) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := `Select c.colname as column_name, c.colno as position, @@ -228,9 +243,7 @@ where t.type = 'T' AND c.tabname = ?` } s = s + f - db.LogSQL(s, args) - - rows, err := db.DB().Query(s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, nil, err } @@ -310,7 +323,7 @@ where t.type = 'T' AND c.tabname = ?` return colSeq, cols, nil } -func (db *db2) GetTables() ([]*schemas.Table, error) { +func (db *db2) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT TABNAME FROM SYSCAT.TABLES WHERE type = 'T' AND OWNERTYPE = 'U'" if len(db.uri.Schema) != 0 { @@ -318,9 +331,7 @@ func (db *db2) GetTables() ([]*schemas.Table, error) { s = s + " AND TABSCHEMA = ?" } - db.LogSQL(s, args) - - rows, err := db.DB().Query(s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -340,7 +351,7 @@ func (db *db2) GetTables() ([]*schemas.Table, error) { return tables, nil } -func (db *db2) GetIndexes(tableName string) (map[string]*schemas.Index, error) { +func (db *db2) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := fmt.Sprintf(`select uniquerule, indname as index_name, @@ -350,9 +361,8 @@ from syscat.indexes WHERE tabname = ?`) args = append(args, db.uri.Schema) s = s + " AND tabschema=?" } - db.LogSQL(s, args) - rows, err := db.DB().Query(s, args...) + rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { return nil, err } @@ -399,7 +409,7 @@ from syscat.indexes WHERE tabname = ?`) } func (db *db2) Filters() []Filter { - return []Filter{&QuoteFilter{}} + return []Filter{} } type db2Driver struct{} diff --git a/go.mod b/go.mod index 7e2bbc2f..27ee1752 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.7.4 github.com/jackc/pgx/v4 v4.12.0 - github.com/ibmdb/go_ibm_db v0.1.0 + github.com/ibmdb/go_ibm_db v0.3.0 github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.10.2 github.com/mattn/go-sqlite3 v1.14.8 diff --git a/go.sum b/go.sum index 58754e7d..e2a816bf 100644 --- a/go.sum +++ b/go.sum @@ -205,6 +205,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/ibmdb/go_ibm_db v0.1.0 h1:Ok7W7wysBUa8eyVYxWLS5vIA0VomTsurK57l5Rah1M8= github.com/ibmdb/go_ibm_db v0.1.0/go.mod h1:nl5aUh1IzBVExcqYXaZLApaq8RUvTEph3VP49UTmEvg= +github.com/ibmdb/go_ibm_db v0.3.0 h1:KCSVFS9eXmlTEFL8ScyROsYWmP02G3eGce7VRAt4Csk= +github.com/ibmdb/go_ibm_db v0.3.0/go.mod h1:nl5aUh1IzBVExcqYXaZLApaq8RUvTEph3VP49UTmEvg= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -- 2.40.1 From 5f2a778d21d6a2e4da227b0ae3a216cce5b54e3f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 11 Sep 2020 15:24:14 +0800 Subject: [PATCH 07/18] Improve chinese README --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 500bb1fb..e2c5165c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -6,7 +6,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) -## Notice +## 注意 v1.0.0 相对于 v0.8.2 有以下不兼容的变更: -- 2.40.1 From d761ecc43d4ef4b7065fe4d8af5d92ac97783c28 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 15 Mar 2021 23:03:58 +0800 Subject: [PATCH 08/18] Improve db2 --- Makefile | 6 +++--- test_db2.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index b66d9958..652824b7 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ TEST_TIDB_PASSWORD ?= TEST_CACHE_ENABLE ?= false TEST_QUOTE_POLICY ?= always -DB2HOME := $(GOPATH)/src/github.com/ibmdb/go_ibm_db/installer +DB2HOME := $(GOPATH)/pkg/mod/github.com/ibmdb/go_ibm_db@v0.3.0/installer DB2_DRIVER_DIR := $(DB2HOME)/clidriver .PHONY: all @@ -156,8 +156,8 @@ test-cockroach\#%: go-check -ignore_update_limit=true -coverprofile=cockroach.$(TEST_COCKROACH_SCHEMA).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic $(DB2_DRIVER_DIR): - GO111MODULE=off go get github.com/ibmdb/go_ibm_db - GO111MODULE=off cd $(DB2HOME) && go run setup.go + go get -d -v github.com/ibmdb/go_ibm_db@v0.3.0 + cd $(DB2HOME) && go run setup.go .PNONY: test-db2 test-db2: go-check $(DB2_DRIVER_DIR) diff --git a/test_db2.sh b/test_db2.sh index 55f7a0de..fee570d0 100755 --- a/test_db2.sh +++ b/test_db2.sh @@ -1,6 +1,6 @@ #!/bin/bash -go get github.com/ibmdb/go_ibm_db +go get -d github.com/ibmdb/go_ibm_db export DB2HOME=$GOPATH/src/github.com/ibmdb/go_ibm_db/installer cur="$PWD" cd $DB2HOME && go run setup.go -- 2.40.1 From 80f0e8e0b2c85f0329fcecb1b9eedbba5e3324fd Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Jun 2021 20:07:05 +0800 Subject: [PATCH 09/18] Fix drone --- .drone.yml | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.drone.yml b/.drone.yml index 34b9a514..a2d59352 100644 --- a/.drone.yml +++ b/.drone.yml @@ -363,6 +363,58 @@ services: commands: - /cockroach/cockroach start --insecure +--- +kind: pipeline +name: test-db2 +depends_on: + - test-cockroach +trigger: + ref: + - refs/heads/master + - refs/pull/*/head +steps: +- name: test-db2 + pull: never + image: golang:1.15 + volumes: + - name: cache + path: /go/pkg/mod + environment: + TEST_DB2_HOST: db2 + TEST_DB2_PORT: 50000 + TEST_DB2_DBNAME: xorm_test + TEST_DB2_USERNAME: sa + TEST_DB2_PASSWORD: xorm_test + commands: + - make test-db2 + - TEST_CACHE_ENABLE=true make test-db2 + +volumes: +- name: cache + host: + path: /tmp/cache + +services: +- name: db2 + pull: default + image: store/ibmcorp/db2_developer_c:11.1.4.4-x86_64 + environment: + LICENSE: accept + DB2INSTANCE: db2inst1 + DB2INST1_PASSWORD: xorm_test + DBNAME: xorm_test + BLU: false + ENABLE_ORACLE_COMPATIBILITY: false + UPDATEAVAIL: NO + TO_CREATE_SAMPLEDB: false + REPODB: false + IS_OSXFS: true + PERSISTENT_HOME: true + HADR_ENABLED: false + ETCD_ENDPOINT: + ETCD_USERNAME: + ETCD_PASSWORD: + --- kind: pipeline name: merge_coverage @@ -374,6 +426,7 @@ depends_on: - test-mssql - test-tidb - test-cockroach + - test-db2 trigger: ref: - refs/heads/master -- 2.40.1 From 96309f04a3109ecdcb57445bf97404c647e8fe63 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Jul 2021 11:12:52 +0800 Subject: [PATCH 10/18] Fix build --- dialects/db2.go | 48 ++++++++++++++++++++++++++++++++++++++++++++- dialects/dialect.go | 15 +------------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/dialects/db2.go b/dialects/db2.go index 1b2f0038..7b420e51 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -6,6 +6,7 @@ package dialects import ( "context" + "database/sql" "errors" "fmt" "strconv" @@ -33,6 +34,23 @@ func (db *db2) Init(uri *URI) error { return db.Base.Init(db, uri) } +func (db *db2) Version(context.Context, core.Queryer) (*schemas.Version, error) { + return nil, fmt.Errorf("not implementation") +} + +func (db *db2) ColumnTypeKind(t string) int { + switch strings.ToUpper(t) { + case "DATE", "DATETIME", "DATETIME2", "TIME": + return schemas.TIME_TYPE + case "VARCHAR", "TEXT", "CHAR", "NVARCHAR", "NCHAR", "NTEXT": + return schemas.TEXT_TYPE + case "FLOAT", "REAL", "BIGINT", "DATETIMEOFFSET", "TINYINT", "SMALLINT", "INT": + return schemas.NUMERIC_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + func (db *db2) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -412,7 +430,35 @@ func (db *db2) Filters() []Filter { return []Filter{} } -type db2Driver struct{} +type db2Driver struct { + baseDriver +} + +func (p *db2Driver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: false, + } +} + +func (g *db2Driver) GenScanResult(colType string) (interface{}, error) { + switch colType { + case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": + var s sql.NullString + return &s, nil + case "NUMBER": + var s sql.NullString + return &s, nil + case "DATE": + var s sql.NullTime + return &s, nil + case "BLOB": + var r sql.RawBytes + return &r, nil + default: + var r sql.RawBytes + return &r, nil + } +} func (p *db2Driver) Parse(driverName, dataSourceName string) (*URI, error) { var dbName string diff --git a/dialects/dialect.go b/dialects/dialect.go index d844aed9..5d65b1f6 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -211,18 +211,6 @@ func regDrvsNDialects() bool { getDriver func() Driver getDialect func() Dialect }{ -<<<<<<< HEAD - "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{} }}, - "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, - "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, - "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, -======= "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{} }}, @@ -232,9 +220,8 @@ func regDrvsNDialects() bool { "sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, - "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, + "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, "go_ibm_db": {"db2", func() Driver { return &db2Driver{} }, func() Dialect { return &db2{} }}, ->>>>>>> 538a3b2 (Use new dialect interface) } for driverName, v := range providedDrvsNDialects { -- 2.40.1 From 1bbf4ffc832fa9e580dfca972c7392b0c5414615 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 23 Jul 2021 12:10:32 +0800 Subject: [PATCH 11/18] Fix driver path --- Makefile | 22 +++++++++++----------- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 652824b7..04e456fd 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,9 @@ TEST_TIDB_PASSWORD ?= TEST_CACHE_ENABLE ?= false TEST_QUOTE_POLICY ?= always -DB2HOME := $(GOPATH)/pkg/mod/github.com/ibmdb/go_ibm_db@v0.3.0/installer -DB2_DRIVER_DIR := $(DB2HOME)/clidriver +DB2ORG := $(GOPATH)/pkg/mod/github.com/ibmdb +DB2HOME := $(DB2ORG)/go_ibm_db@v0.4.1/installer +DB2_DRIVER_DIR := $(DB2ORG)/clidriver .PHONY: all all: build @@ -156,23 +157,23 @@ test-cockroach\#%: go-check -ignore_update_limit=true -coverprofile=cockroach.$(TEST_COCKROACH_SCHEMA).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic $(DB2_DRIVER_DIR): - go get -d -v github.com/ibmdb/go_ibm_db@v0.3.0 + go get -d -v github.com/ibmdb/go_ibm_db cd $(DB2HOME) && go run setup.go .PNONY: test-db2 test-db2: go-check $(DB2_DRIVER_DIR) - CGO_CFLAGS=-I$(DB2HOME)/clidriver/include \ - CGO_LDFLAGS=-L$(DB2HOME)/clidriver/lib \ - DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2HOME)/clidriver/lib \ + CGO_CFLAGS=-I$(DB2_DRIVER_DIR)/include \ + CGO_LDFLAGS=-L$(DB2_DRIVER_DIR)/lib \ + DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2_DRIVER_DIR)/lib \ $(GO) test -race -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ -conn_str="HOSTNAME=$(TEST_DB2_HOST);DATABASE=$(TEST_DB2_DBNAME);PORT=$(TEST_DB2_PORT);UID=$(TEST_DB2_USERNAME);PWD=$(TEST_DB2_PASSWORD)" \ -coverprofile=db2.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-db2\#% test-db2\#%: go-check - CGO_CFLAGS=-I$(DB2HOME)/clidriver/include \ - CGO_LDFLAGS=-L$(DB2HOME)/clidriver/lib \ - DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2HOME)/clidriver/lib \ + CGO_CFLAGS=-I$(DB2_DRIVER_DIR)/include \ + CGO_LDFLAGS=-L$(DB2_DRIVER_DIR)/lib \ + DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2_DRIVER_DIR)/lib \ $(GO) test -race -run $* -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ -conn_str="HOSTNAME=$(TEST_DB2_HOST);DATABASE=$(TEST_DB2_DBNAME);PORT=$(TEST_DB2_PORT);UID=$(TEST_DB2_USERNAME);PWD=$(TEST_DB2_PASSWORD)" \ -coverprofile=db2.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic @@ -269,8 +270,7 @@ test-sqlite-schema: go-check .PHONY: test-sqlite\#% test-sqlite\#%: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ - -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic - + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomicattt .PNONY: test-tidb test-tidb: go-check diff --git a/go.mod b/go.mod index 27ee1752..3eb65092 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.7.4 github.com/jackc/pgx/v4 v4.12.0 - github.com/ibmdb/go_ibm_db v0.3.0 + github.com/ibmdb/go_ibm_db v0.4.1 github.com/json-iterator/go v1.1.11 github.com/lib/pq v1.10.2 github.com/mattn/go-sqlite3 v1.14.8 diff --git a/go.sum b/go.sum index e2a816bf..88962f03 100644 --- a/go.sum +++ b/go.sum @@ -207,6 +207,8 @@ github.com/ibmdb/go_ibm_db v0.1.0 h1:Ok7W7wysBUa8eyVYxWLS5vIA0VomTsurK57l5Rah1M8 github.com/ibmdb/go_ibm_db v0.1.0/go.mod h1:nl5aUh1IzBVExcqYXaZLApaq8RUvTEph3VP49UTmEvg= github.com/ibmdb/go_ibm_db v0.3.0 h1:KCSVFS9eXmlTEFL8ScyROsYWmP02G3eGce7VRAt4Csk= github.com/ibmdb/go_ibm_db v0.3.0/go.mod h1:nl5aUh1IzBVExcqYXaZLApaq8RUvTEph3VP49UTmEvg= +github.com/ibmdb/go_ibm_db v0.4.1 h1:IYZqoKTzD9xtkzLIkp8u6zzg7/4v7nFOfHzF79agvak= +github.com/ibmdb/go_ibm_db v0.4.1/go.mod h1:nl5aUh1IzBVExcqYXaZLApaq8RUvTEph3VP49UTmEvg= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -- 2.40.1 From 3e451201601ae0970473bba2a2a2949f43f1c8ca Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 13:37:02 +0800 Subject: [PATCH 12/18] Fix test --- Makefile | 10 ++-------- dialects/db2.go | 14 ++++++++++++-- .../engine_db2_test.go | 2 +- test_db2.sh | 11 ----------- 4 files changed, 15 insertions(+), 22 deletions(-) rename xorm_db2_test.go => integrations/engine_db2_test.go (91%) delete mode 100755 test_db2.sh diff --git a/Makefile b/Makefile index 04e456fd..c979b05a 100644 --- a/Makefile +++ b/Makefile @@ -162,19 +162,13 @@ $(DB2_DRIVER_DIR): .PNONY: test-db2 test-db2: go-check $(DB2_DRIVER_DIR) - CGO_CFLAGS=-I$(DB2_DRIVER_DIR)/include \ - CGO_LDFLAGS=-L$(DB2_DRIVER_DIR)/lib \ - DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2_DRIVER_DIR)/lib \ - $(GO) test -race -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -tags=db2 -db=go_ibm_db -cache=$(TEST_CACHE_ENABLE) \ -conn_str="HOSTNAME=$(TEST_DB2_HOST);DATABASE=$(TEST_DB2_DBNAME);PORT=$(TEST_DB2_PORT);UID=$(TEST_DB2_USERNAME);PWD=$(TEST_DB2_PASSWORD)" \ -coverprofile=db2.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-db2\#% test-db2\#%: go-check - CGO_CFLAGS=-I$(DB2_DRIVER_DIR)/include \ - CGO_LDFLAGS=-L$(DB2_DRIVER_DIR)/lib \ - DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH):$(DB2_DRIVER_DIR)/lib \ - $(GO) test -race -run $* -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ + $(GO) test $(INTEGRATION_PACKAGES) -v -run $* -db=go_ibm_db -tags=db2 -cache=$(TEST_CACHE_ENABLE) \ -conn_str="HOSTNAME=$(TEST_DB2_HOST);DATABASE=$(TEST_DB2_DBNAME);PORT=$(TEST_DB2_PORT);UID=$(TEST_DB2_USERNAME);PWD=$(TEST_DB2_PASSWORD)" \ -coverprofile=db2.$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic diff --git a/dialects/db2.go b/dialects/db2.go index 7b420e51..0a106d06 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -57,6 +57,10 @@ func (db *db2) SQLType(c *schemas.Column) string { case schemas.TinyInt: res = schemas.SmallInt return res + case schemas.UnsignedBigInt: + res = schemas.BigInt + case schemas.UnsignedInt: + res = schemas.BigInt case schemas.Bit: res = schemas.Boolean return res @@ -180,9 +184,9 @@ func (db *db2) SetQuotePolicy(quotePolicy QuotePolicy) { func (db *db2) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { if len(db.uri.Schema) == 0 { - return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE tablename = ?`, tableName) + return db.HasRecords(queryer, ctx, `SELECT tabname FROM syscat.tables WHERE tabname = ?`, tableName) } - return db.HasRecords(queryer, ctx, `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, + return db.HasRecords(queryer, ctx, `SELECT tabname FROM syscat.tables WHERE tabschema = ? AND tabname = ?`, db.uri.Schema, tableName, ) } @@ -196,6 +200,12 @@ func (db *db2) ModifyColumnSQL(tableName string, col *schemas.Column) string { db.uri.Schema, tableName, col.Name, db.SQLType(col)) } +// DropTableSQL returns drop table SQL +func (db *db2) DropTableSQL(tableName string) (string, bool) { + quote := db.Quoter().Quote + return fmt.Sprintf("DROP TABLE %s", quote(tableName)), false +} + func (db *db2) DropIndexSQL(tableName string, index *schemas.Index) string { quote := db.Quoter().Quote idxName := index.Name diff --git a/xorm_db2_test.go b/integrations/engine_db2_test.go similarity index 91% rename from xorm_db2_test.go rename to integrations/engine_db2_test.go index cb0cea5d..8bbc72f6 100644 --- a/xorm_db2_test.go +++ b/integrations/engine_db2_test.go @@ -4,7 +4,7 @@ // +build db2 -package xorm +package integrations import ( _ "github.com/ibmdb/go_ibm_db" diff --git a/test_db2.sh b/test_db2.sh deleted file mode 100755 index fee570d0..00000000 --- a/test_db2.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -go get -d github.com/ibmdb/go_ibm_db -export DB2HOME=$GOPATH/src/github.com/ibmdb/go_ibm_db/installer -cur="$PWD" -cd $DB2HOME && go run setup.go -export CGO_CFLAGS=-I$DB2HOME/include -export CGO_LDFLAGS=-L$DB2HOME/lib -export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$DB2HOME/clidriver/lib -cd $cur -go test -db=go_ibm_db -tags=db2 -conn_str="HOSTNAME=localhost;DATABASE=testdb;PORT=50000;UID=db2inst1;PWD=123#2@23" \ No newline at end of file -- 2.40.1 From e502385b12496b3ac7940d2a74891e6f2ad1c485 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 16:19:11 +0800 Subject: [PATCH 13/18] Fix tests --- dialects/db2.go | 6 +-- integrations/session_cols_test.go | 6 +-- internal/utils/name.go | 6 +++ internal/utils/slice.go | 9 +++++ schemas/type.go | 1 + session_insert.go | 61 ++++++++++++++++++++++--------- 6 files changed, 64 insertions(+), 25 deletions(-) diff --git a/dialects/db2.go b/dialects/db2.go index 0a106d06..74f2d750 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -61,7 +61,7 @@ func (db *db2) SQLType(c *schemas.Column) string { res = schemas.BigInt case schemas.UnsignedInt: res = schemas.BigInt - case schemas.Bit: + case schemas.Bit, schemas.Bool, schemas.Boolean: res = schemas.Boolean return res case schemas.Binary, schemas.VarBinary: @@ -82,10 +82,6 @@ func (db *db2) SQLType(c *schemas.Column) string { res = t } - if strings.EqualFold(res, "bool") { - // for bool, we don't need length information - return res - } hasLen1 := (c.Length > 0) hasLen2 := (c.Length2 > 0) diff --git a/integrations/session_cols_test.go b/integrations/session_cols_test.go index b74c6f8a..1b91e0bb 100644 --- a/integrations/session_cols_test.go +++ b/integrations/session_cols_test.go @@ -52,11 +52,11 @@ func TestSetExpr(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - tableName := testEngine.TableName(new(UserExprIssue), true) + tableName := testEngine.Quote(testEngine.TableName(new(UserExprIssue), true)) cnt, err = testEngine.SetExpr("issue_id", - builder.Select("id"). + builder.Select("`id`"). From(tableName). - Where(builder.Eq{"id": issue.Id})). + Where(builder.Eq{"`id`": issue.Id})). ID(1). Update(new(UserExpr)) assert.NoError(t, err) diff --git a/internal/utils/name.go b/internal/utils/name.go index 840dd9e8..aeef683d 100644 --- a/internal/utils/name.go +++ b/internal/utils/name.go @@ -6,9 +6,15 @@ package utils import ( "fmt" + "strings" ) // IndexName returns index name func IndexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } + +// SeqName returns sequence name for some table +func SeqName(tableName string) string { + return "SEQ_" + strings.ToUpper(tableName) +} diff --git a/internal/utils/slice.go b/internal/utils/slice.go index 89685706..b568f6f5 100644 --- a/internal/utils/slice.go +++ b/internal/utils/slice.go @@ -20,3 +20,12 @@ func SliceEq(left, right []string) bool { } return true } + +func IndexSlice(s []string, c string) int { + for i, ss := range s { + if c == ss { + return i + } + } + return -1 +} diff --git a/schemas/type.go b/schemas/type.go index cf730134..c66824a6 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -22,6 +22,7 @@ const ( MYSQL DBType = "mysql" MSSQL DBType = "mssql" ORACLE DBType = "oracle" + DB2 DBType = "db2" ) // SQLType represents SQL types diff --git a/session_insert.go b/session_insert.go index a8f365c7..d4555730 100644 --- a/session_insert.go +++ b/session_insert.go @@ -307,16 +307,53 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { // if there is auto increment column and driver don't support return it if len(table.AutoIncrement) > 0 && !session.engine.driver.Features().SupportReturnInsertedID { - var sql = sqlStr - if session.engine.dialect.URI().DBType == schemas.ORACLE { - sql = "select seq_atable.currval from dual" + var sql string + var newArgs []interface{} + var needCommit bool + var id int64 + if session.engine.dialect.URI().DBType == schemas.DB2 || session.engine.dialect.URI().DBType == schemas.ORACLE { + if session.isAutoCommit { // if it's not in transaction + if err := session.Begin(); err != nil { + return 0, err + } + needCommit = true + } + _, err := session.exec(sqlStr, args...) + if err != nil { + return 0, err + } + i := utils.IndexSlice(colNames, table.AutoIncrement) + if i > -1 { + id, err = convert.AsInt64(args[i]) + if err != nil { + return 0, err + } + } else { + if session.engine.dialect.URI().DBType == schemas.ORACLE { + sql = fmt.Sprintf("select %s.currval from dual", utils.SeqName(tableName)) + } else if session.engine.dialect.URI().DBType == schemas.DB2 { + sql = "select IDENTITY_VAL_LOCAL() as id FROM sysibm.sysdummy1" + } + } + } else { + sql = sqlStr + newArgs = args } - rows, err := session.queryRows(sql, args...) - if err != nil { - return 0, err + if id == 0 { + err := session.queryRow(sql, newArgs...).Scan(&id) + if err != nil { + return 0, err + } + if needCommit { + if err := session.Commit(); err != nil { + return 0, err + } + } + if id == 0 { + return 0, errors.New("insert successfully but not returned id") + } } - defer rows.Close() defer handleAfterInsertProcessorFunc(bean) @@ -331,16 +368,6 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { } } - var id int64 - if !rows.Next() { - if rows.Err() != nil { - return 0, rows.Err() - } - return 0, errors.New("insert successfully but not returned id") - } - if err := rows.Scan(&id); err != nil { - return 1, err - } aiValue, err := table.AutoIncrColumn().ValueOf(bean) if err != nil { session.engine.logger.Errorf("%v", err) -- 2.40.1 From 78d3504360ba174fd6255bd5c80f9c5637145632 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 17:43:26 +0800 Subject: [PATCH 14/18] Fix tests --- dialects/db2.go | 53 +++++++++++++++++++------------ dialects/dialect.go | 37 ++++++++++++++------- dialects/postgres.go | 37 ++++++++++----------- integrations/engine_test.go | 2 +- integrations/session_cond_test.go | 26 +++++++-------- 5 files changed, 89 insertions(+), 66 deletions(-) diff --git a/dialects/db2.go b/dialects/db2.go index 74f2d750..2ea65019 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -38,6 +38,12 @@ func (db *db2) Version(context.Context, core.Queryer) (*schemas.Version, error) return nil, fmt.Errorf("not implementation") } +func (db *db2) Features() *DialectFeatures { + return &DialectFeatures{ + DefaultClause: "WITH DEFAULT", + } +} + func (db *db2) ColumnTypeKind(t string) int { switch strings.ToUpper(t) { case "DATE", "DATETIME", "DATETIME2", "TIME": @@ -58,9 +64,9 @@ func (db *db2) SQLType(c *schemas.Column) string { res = schemas.SmallInt return res case schemas.UnsignedBigInt: - res = schemas.BigInt + return schemas.BigInt case schemas.UnsignedInt: - res = schemas.BigInt + return schemas.BigInt case schemas.Bit, schemas.Bool, schemas.Boolean: res = schemas.Boolean return res @@ -119,35 +125,42 @@ func (db *db2) IndexOnTable() bool { } func (db *db2) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { - var sql string - sql = "CREATE TABLE " if tableName == "" { tableName = table.Name } - sql += db.Quoter().Quote(tableName) + " (" + quoter := db.Quoter() + var b strings.Builder + b.WriteString("CREATE TABLE ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" (") - pkList := table.PrimaryKeys - - for _, colName := range table.ColumnsSeq() { + for i, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) - s, _ := ColumnString(db, col, false) - sql += s - if col.IsAutoIncrement { - sql += " GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1 )" + if !col.DefaultIsEmpty { + col.Nullable = false + } + s, _ := ColumnString(db, col, false) + b.WriteString(s) + + if col.IsAutoIncrement { + b.WriteString(" GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1)") + } + + if i != len(table.ColumnsSeq())-1 { + b.WriteString(", ") } - sql = strings.TrimSpace(sql) - sql += ", " } - if len(pkList) > 0 { - sql += "PRIMARY KEY ( " - sql += db.Quoter().Join(pkList, ",") - sql += " ), " + if len(table.PrimaryKeys) > 0 { + b.WriteString(", PRIMARY KEY (") + b.WriteString(quoter.Join(table.PrimaryKeys, ",")) + b.WriteString(")") } - sql = sql[:len(sql)-2] + ")" - return []string{sql}, false + b.WriteString(")") + + return []string{b.String()}, true } func (db *db2) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { diff --git a/dialects/dialect.go b/dialects/dialect.go index 5d65b1f6..12220523 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -32,17 +32,21 @@ type URI struct { // SetSchema set schema func (uri *URI) SetSchema(schema string) { - // hack me if uri.DBType == schemas.POSTGRES { uri.Schema = strings.TrimSpace(schema) } } +type DialectFeatures struct { + DefaultClause string // default key word +} + // Dialect represents a kind of database type Dialect interface { Init(*URI) error URI() *URI Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) + Features() *DialectFeatures SQLType(*schemas.Column) string Alias(string) string // return what a sql type's alias of @@ -103,6 +107,12 @@ func (db *Base) URI() *URI { return db.uri } +func (db *Base) Features() *DialectFeatures { + return &DialectFeatures{ + DefaultClause: "DEFAULT", + } +} + // DropTableSQL returns drop table SQL func (db *Base) DropTableSQL(tableName string) (string, bool) { quote := db.dialect.Quoter().Quote @@ -253,43 +263,46 @@ func ColumnString(dialect Dialect, col *schemas.Column, includePrimaryKey bool) return "", err } - if err := bd.WriteByte(' '); err != nil { - return "", err - } - if includePrimaryKey && col.IsPrimaryKey { - if _, err := bd.WriteString("PRIMARY KEY "); err != nil { + if _, err := bd.WriteString(" PRIMARY KEY"); err != nil { return "", err } if col.IsAutoIncrement { - if _, err := bd.WriteString(dialect.AutoIncrStr()); err != nil { + if err := bd.WriteByte(' '); err != nil { return "", err } - if err := bd.WriteByte(' '); err != nil { + if _, err := bd.WriteString(dialect.AutoIncrStr()); err != nil { return "", err } } } if col.Default != "" { - if _, err := bd.WriteString("DEFAULT "); err != nil { + if err := bd.WriteByte(' '); err != nil { return "", err } - if _, err := bd.WriteString(col.Default); err != nil { + if _, err := bd.WriteString(dialect.Features().DefaultClause); err != nil { return "", err } if err := bd.WriteByte(' '); 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 { + if _, err := bd.WriteString("NULL"); err != nil { return "", err } } else { - if _, err := bd.WriteString("NOT NULL "); err != nil { + if _, err := bd.WriteString("NOT NULL"); err != nil { return "", err } } diff --git a/dialects/postgres.go b/dialects/postgres.go index 96ebfc85..d40f4aad 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -966,38 +966,35 @@ func (db *postgres) AutoIncrStr() string { } func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) { - var sql string - sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { tableName = table.Name } quoter := db.Quoter() - sql += quoter.Quote(tableName) - sql += " (" + var b strings.Builder + b.WriteString("CREATE TABLE IF NOT EXIST ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" (") - if len(table.ColumnsSeq()) > 0 { - pkList := table.PrimaryKeys + for i, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + s, _ := ColumnString(db, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1) + b.WriteString(s) - for _, colName := range table.ColumnsSeq() { - col := table.GetColumn(colName) - s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1) - sql += s - sql = strings.TrimSpace(sql) - sql += ", " + if len(table.PrimaryKeys) > 1 { + b.WriteString("PRIMARY KEY ( ") + b.WriteString(quoter.Join(table.PrimaryKeys, ",")) + b.WriteString(" )") } - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += quoter.Join(pkList, ",") - sql += " ), " + if i != len(table.ColumnsSeq())-1 { + b.WriteString(", ") } - - sql = sql[:len(sql)-2] } - sql += ")" - return []string{sql}, true + b.WriteString(")") + + return []string{b.String()}, false } func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 02b35a2c..cfcdd985 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -126,7 +126,7 @@ func TestDump(t *testing.T) { assert.NoError(t, err) assert.NoError(t, sess.Commit()) - for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} { + for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL, schemas.DB2} { name := fmt.Sprintf("dump_%v.sql", tp) t.Run(name, func(t *testing.T) { assert.NoError(t, testEngine.DumpAllToFile(name, tp)) diff --git a/integrations/session_cond_test.go b/integrations/session_cond_test.go index a0a91cad..05972ecc 100644 --- a/integrations/session_cond_test.go +++ b/integrations/session_cond_test.go @@ -37,49 +37,49 @@ func TestBuilder(t *testing.T) { assert.NoError(t, err) var cond Condition - has, err := testEngine.Where(builder.Eq{"col_name": "col1"}).Get(&cond) + has, err := testEngine.Where(builder.Eq{"`col_name`": "col1"}).Get(&cond) assert.NoError(t, err) assert.Equal(t, true, has, "records should exist") - has, err = testEngine.Where(builder.Eq{"col_name": "col1"}. - And(builder.Eq{"op": OpEqual})). + has, err = testEngine.Where(builder.Eq{"`col_name`": "col1"}. + And(builder.Eq{"`op`": OpEqual})). NoAutoCondition(). Get(&cond) assert.NoError(t, err) assert.Equal(t, true, has, "records should exist") - has, err = testEngine.Where(builder.Eq{"col_name": "col1", "op": OpEqual, "value": "1"}). + has, err = testEngine.Where(builder.Eq{"`col_name`": "col1", "`op`": OpEqual, "`value`": "1"}). NoAutoCondition(). Get(&cond) assert.NoError(t, err) assert.Equal(t, true, has, "records should exist") - has, err = testEngine.Where(builder.Eq{"col_name": "col1"}. - And(builder.Neq{"op": OpEqual})). + has, err = testEngine.Where(builder.Eq{"`col_name`": "col1"}. + And(builder.Neq{"`op`": OpEqual})). NoAutoCondition(). Get(&cond) assert.NoError(t, err) assert.Equal(t, false, has, "records should not exist") var conds []Condition - err = testEngine.Where(builder.Eq{"col_name": "col1"}. - And(builder.Eq{"op": OpEqual})). + err = testEngine.Where(builder.Eq{"`col_name`": "col1"}. + And(builder.Eq{"`op`": OpEqual})). Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") conds = make([]Condition, 0) - err = testEngine.Where(builder.Like{"col_name", "col"}).Find(&conds) + err = testEngine.Where(builder.Like{"`col_name`", "col"}).Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") conds = make([]Condition, 0) - err = testEngine.Where(builder.Expr("col_name = ?", "col1")).Find(&conds) + err = testEngine.Where(builder.Expr("`col_name` = ?", "col1")).Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") conds = make([]Condition, 0) - err = testEngine.Where(builder.In("col_name", "col1", "col2")).Find(&conds) + err = testEngine.Where(builder.In("`col_name`", "col1", "col2")).Find(&conds) assert.NoError(t, err) assert.EqualValues(t, 1, len(conds), "records should exist") @@ -91,8 +91,8 @@ func TestBuilder(t *testing.T) { // complex condtions var where = builder.NewCond() if true { - where = where.And(builder.Eq{"col_name": "col1"}) - where = where.Or(builder.And(builder.In("col_name", "col1", "col2"), builder.Expr("col_name = ?", "col1"))) + where = where.And(builder.Eq{"`col_name`": "col1"}) + where = where.Or(builder.And(builder.In("`col_name`", "col1", "col2"), builder.Expr("`col_name` = ?", "col1"))) } conds = make([]Condition, 0) -- 2.40.1 From 1abb80f258b6761dfa1f1bdeac16e8164b308dd8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 17:46:58 +0800 Subject: [PATCH 15/18] Fix drone --- .drone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.drone.yml b/.drone.yml index a2d59352..1215a519 100644 --- a/.drone.yml +++ b/.drone.yml @@ -385,6 +385,10 @@ steps: TEST_DB2_DBNAME: xorm_test TEST_DB2_USERNAME: sa TEST_DB2_PASSWORD: xorm_test + DB2HOME: /go/pkg/mod/github.com/ibmdb/clidriver + CGO_CFLAGS: -I$DB2HOME/include + CGO_LDFLAGS: -L$DB2HOME/lib + LD_LIBRARY_PATH: $DB2HOME/lib commands: - make test-db2 - TEST_CACHE_ENABLE=true make test-db2 -- 2.40.1 From 815db53e9db536aedf5e59f12773099328ab7dfe Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 20:37:25 +0800 Subject: [PATCH 16/18] Fix db2 --- dialects/db2.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dialects/db2.go b/dialects/db2.go index 2ea65019..f4e4e979 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -67,11 +67,13 @@ func (db *db2) SQLType(c *schemas.Column) string { return schemas.BigInt case schemas.UnsignedInt: return schemas.BigInt + case schemas.Int, schemas.Integer: + return schemas.Integer case schemas.Bit, schemas.Bool, schemas.Boolean: res = schemas.Boolean return res - case schemas.Binary, schemas.VarBinary: - return schemas.Bytea + case schemas.Binary: + res = schemas.Binary case schemas.DateTime: res = schemas.TimeStamp case schemas.TimeStampz: @@ -82,8 +84,13 @@ func (db *db2) SQLType(c *schemas.Column) string { res = schemas.Varchar case schemas.Uuid: return schemas.Uuid + case schemas.VarBinary, schemas.Bytea: + res = schemas.VarBinary + if c.Length == 0 { + return res + "(MAX)" + } case schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: - return schemas.Bytea + return schemas.Blob default: res = t } -- 2.40.1 From 2f60b3be0b4a8687444782b832699935776dffbb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 20:49:01 +0800 Subject: [PATCH 17/18] Fix db2 --- session_insert.go | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/session_insert.go b/session_insert.go index d4555730..07a63648 100644 --- a/session_insert.go +++ b/session_insert.go @@ -311,8 +311,15 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { var newArgs []interface{} var needCommit bool var id int64 + var i = utils.IndexSlice(colNames, table.AutoIncrement) + if i > -1 { + id, err = convert.AsInt64(args[i]) + if err != nil { + return 0, err + } + } if session.engine.dialect.URI().DBType == schemas.DB2 || session.engine.dialect.URI().DBType == schemas.ORACLE { - if session.isAutoCommit { // if it's not in transaction + if id == 0 && session.isAutoCommit { // if it's not in transaction if err := session.Begin(); err != nil { return 0, err } @@ -320,20 +327,16 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { } _, err := session.exec(sqlStr, args...) if err != nil { + if needCommit { + session.Rollback() + } return 0, err } - i := utils.IndexSlice(colNames, table.AutoIncrement) - if i > -1 { - id, err = convert.AsInt64(args[i]) - if err != nil { - return 0, err - } - } else { - if session.engine.dialect.URI().DBType == schemas.ORACLE { - sql = fmt.Sprintf("select %s.currval from dual", utils.SeqName(tableName)) - } else if session.engine.dialect.URI().DBType == schemas.DB2 { - sql = "select IDENTITY_VAL_LOCAL() as id FROM sysibm.sysdummy1" - } + + if session.engine.dialect.URI().DBType == schemas.ORACLE { + sql = fmt.Sprintf("select %s.currval from dual", utils.SeqName(tableName)) + } else if session.engine.dialect.URI().DBType == schemas.DB2 { + sql = "select IDENTITY_VAL_LOCAL() as id FROM sysibm.sysdummy1" } } else { sql = sqlStr @@ -343,6 +346,9 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { if id == 0 { err := session.queryRow(sql, newArgs...).Scan(&id) if err != nil { + if needCommit { + session.Rollback() + } return 0, err } if needCommit { -- 2.40.1 From 889333da7b59e86330d4c6021425d4705bc5f3ec Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Aug 2021 22:47:02 +0800 Subject: [PATCH 18/18] Fix test --- dialects/db2.go | 26 +++++++++++++++-- integrations/engine_test.go | 2 +- integrations/session_cond_test.go | 2 +- integrations/session_count_test.go | 12 ++++---- integrations/session_exist_test.go | 46 +++++++++++++++--------------- integrations/session_find_test.go | 8 +++--- 6 files changed, 58 insertions(+), 38 deletions(-) diff --git a/dialects/db2.go b/dialects/db2.go index f4e4e979..0c973a7c 100644 --- a/dialects/db2.go +++ b/dialects/db2.go @@ -34,8 +34,28 @@ func (db *db2) Init(uri *URI) error { return db.Base.Init(db, uri) } -func (db *db2) Version(context.Context, core.Queryer) (*schemas.Version, error) { - return nil, fmt.Errorf("not implementation") +func (db *db2) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "SELECT service_level, fixpack_num FROM TABLE(sysproc.env_get_inst_info()) as INSTANCEINFO") + if err != nil { + return nil, err + } + defer rows.Close() + + if rows.Next() { + var serviceLevel, fixpackNum string + if err := rows.Scan(&serviceLevel, &fixpackNum); err != nil { + return nil, err + } + + parts := strings.Split(serviceLevel, " ") + return &schemas.Version{ + Number: parts[1], + Level: fixpackNum, + Edition: parts[0], + }, nil + } + + return nil, rows.Err() } func (db *db2) Features() *DialectFeatures { @@ -65,7 +85,7 @@ func (db *db2) SQLType(c *schemas.Column) string { return res case schemas.UnsignedBigInt: return schemas.BigInt - case schemas.UnsignedInt: + case schemas.UnsignedInt, schemas.BigInt: return schemas.BigInt case schemas.Int, schemas.Integer: return schemas.Integer diff --git a/integrations/engine_test.go b/integrations/engine_test.go index cfcdd985..3233962d 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -169,7 +169,7 @@ func TestDumpTables(t *testing.T) { assert.NoError(t, err) assert.NoError(t, sess.Commit()) - for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} { + for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL, schemas.DB2} { name := fmt.Sprintf("dump_%v-table.sql", tp) t.Run(name, func(t *testing.T) { assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, name, tp)) diff --git a/integrations/session_cond_test.go b/integrations/session_cond_test.go index 05972ecc..4e895c49 100644 --- a/integrations/session_cond_test.go +++ b/integrations/session_cond_test.go @@ -215,7 +215,7 @@ func TestFindAndCount(t *testing.T) { assert.NoError(t, err) var results []FindAndCount - sess := testEngine.Where("name = ?", "test1") + sess := testEngine.Where("`name` = ?", "test1") conds := sess.Conds() err = sess.Find(&results) assert.NoError(t, err) diff --git a/integrations/session_count_test.go b/integrations/session_count_test.go index 1517dede..e49b6045 100644 --- a/integrations/session_count_test.go +++ b/integrations/session_count_test.go @@ -63,7 +63,7 @@ func TestSQLCount(t *testing.T) { assertSync(t, new(UserinfoCount2), new(UserinfoBooks)) - total, err := testEngine.SQL("SELECT count(id) FROM " + testEngine.TableName("userinfo_count2", true)). + total, err := testEngine.SQL("SELECT count(`id`) FROM " + testEngine.Quote(testEngine.TableName("userinfo_count2", true))). Count() assert.NoError(t, err) assert.EqualValues(t, 0, total) @@ -89,7 +89,7 @@ func TestCountWithOthers(t *testing.T) { }) assert.NoError(t, err) - total, err := testEngine.OrderBy("id desc").Limit(1).Count(new(CountWithOthers)) + total, err := testEngine.OrderBy("`id` desc").Limit(1).Count(new(CountWithOthers)) assert.NoError(t, err) assert.EqualValues(t, 2, total) } @@ -118,11 +118,11 @@ func TestWithTableName(t *testing.T) { }) assert.NoError(t, err) - total, err := testEngine.OrderBy("id desc").Count(new(CountWithTableName)) + total, err := testEngine.OrderBy("`id` desc").Count(new(CountWithTableName)) assert.NoError(t, err) assert.EqualValues(t, 2, total) - total, err = testEngine.OrderBy("id desc").Count(CountWithTableName{}) + total, err = testEngine.OrderBy("`id` desc").Count(CountWithTableName{}) assert.NoError(t, err) assert.EqualValues(t, 2, total) } @@ -146,7 +146,7 @@ func TestCountWithSelectCols(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 2, total) - total, err = testEngine.Select("count(id)").Count(CountWithTableName{}) + total, err = testEngine.Select("count(`id`)").Count(CountWithTableName{}) assert.NoError(t, err) assert.EqualValues(t, 2, total) } @@ -166,7 +166,7 @@ func TestCountWithGroupBy(t *testing.T) { }) assert.NoError(t, err) - cnt, err := testEngine.GroupBy("name").Count(new(CountWithTableName)) + cnt, err := testEngine.GroupBy("`name`").Count(new(CountWithTableName)) assert.NoError(t, err) assert.EqualValues(t, 2, cnt) } diff --git a/integrations/session_exist_test.go b/integrations/session_exist_test.go index 29546376..c88933ef 100644 --- a/integrations/session_exist_test.go +++ b/integrations/session_exist_test.go @@ -48,19 +48,19 @@ func TestExistStruct(t *testing.T) { assert.NoError(t, err) assert.False(t, has) - has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{}) + has, err = testEngine.Where("`name` = ?", "test1").Exist(&RecordExist{}) assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.Where("name = ?", "test2").Exist(&RecordExist{}) + has, err = testEngine.Where("`name` = ?", "test2").Exist(&RecordExist{}) assert.NoError(t, err) assert.False(t, has) - has, err = testEngine.SQL("select * from "+testEngine.TableName("record_exist", true)+" where name = ?", "test1").Exist() + has, err = testEngine.SQL("select * from "+testEngine.Quote(testEngine.TableName("record_exist", true))+" where `name` = ?", "test1").Exist() assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.SQL("select * from "+testEngine.TableName("record_exist", true)+" where name = ?", "test2").Exist() + has, err = testEngine.SQL("select * from "+testEngine.Quote(testEngine.TableName("record_exist", true))+" where `name` = ?", "test2").Exist() assert.NoError(t, err) assert.False(t, has) @@ -68,11 +68,11 @@ func TestExistStruct(t *testing.T) { assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist() + has, err = testEngine.Table("record_exist").Where("`name` = ?", "test1").Exist() assert.NoError(t, err) assert.True(t, has) - has, err = testEngine.Table("record_exist").Where("name = ?", "test2").Exist() + has, err = testEngine.Table("record_exist").Where("`name` = ?", "test2").Exist() assert.NoError(t, err) assert.False(t, has) @@ -124,43 +124,43 @@ func TestExistStructForJoin(t *testing.T) { defer session.Close() session.Table("number"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("number.lid = ?", 1) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`number`.`lid` = ?", 1) has, err := session.Exist() assert.NoError(t, err) assert.True(t, has) session.Table("number"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("number.lid = ?", 2) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`number`.`lid` = ?", 2) has, err = session.Exist() assert.NoError(t, err) assert.False(t, has) session.Table("number"). Select("order_list.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("order_list.id = ?", 1) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`order_list`.`id` = ?", 1) has, err = session.Exist() assert.NoError(t, err) assert.True(t, has) session.Table("number"). Select("player.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid"). - Where("player.id = ?", 2) + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`"). + Where("`player`.`id` = ?", 2) has, err = session.Exist() assert.NoError(t, err) assert.False(t, has) session.Table("number"). Select("player.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid") + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`") has, err = session.Exist() assert.NoError(t, err) assert.True(t, has) @@ -174,15 +174,15 @@ func TestExistStructForJoin(t *testing.T) { session.Table("number"). Select("player.id"). - Join("INNER", "order_list", "order_list.id = number.lid"). - Join("LEFT", "player", "player.id = order_list.eid") + Join("INNER", "order_list", "`order_list`.`id` = `number`.`lid`"). + Join("LEFT", "player", "`player`.`id` = `order_list`.`eid`") has, err = session.Exist() assert.Error(t, err) assert.False(t, has) session.Table("number"). Select("player.id"). - Join("LEFT", "player", "player.id = number.lid") + Join("LEFT", "player", "`player`.`id` = `number`.`lid`") has, err = session.Exist() assert.NoError(t, err) assert.True(t, has) diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 1cbf5e42..44e5ef37 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -56,8 +56,8 @@ func TestJoinLimit(t *testing.T) { var salaries []Salary err = testEngine.Table("salary"). - Join("INNER", "check_list", "check_list.id = salary.lid"). - Join("LEFT", "empsetting", "empsetting.id = check_list.eid"). + Join("INNER", "check_list", "`check_list`.`id` = `salary`.`lid`"). + Join("LEFT", "empsetting", "`empsetting`.`id` = `check_list`.`eid`"). Limit(10, 0). Find(&salaries) assert.NoError(t, err) @@ -69,10 +69,10 @@ func TestWhere(t *testing.T) { assertSync(t, new(Userinfo)) users := make([]Userinfo, 0) - err := testEngine.Where("id > ?", 2).Find(&users) + err := testEngine.Where("`id` > ?", 2).Find(&users) assert.NoError(t, err) - err = testEngine.Where("id > ?", 2).And("id < ?", 10).Find(&users) + err = testEngine.Where("`id` > ?", 2).And("`id` < ?", 10).Find(&users) assert.NoError(t, err) } -- 2.40.1