From af328951fb0b877a5636c1b4c7a0c300f65d2929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=BC=E7=A0=BC?= Date: Mon, 23 Jun 2014 21:46:08 +0800 Subject: [PATCH 01/15] pqsql-uuid --- postgres_dialect.go | 150 +++++++++++++++++++++++--------------------- 1 file changed, 80 insertions(+), 70 deletions(-) diff --git a/postgres_dialect.go b/postgres_dialect.go index a088664c..ff5216c6 100644 --- a/postgres_dialect.go +++ b/postgres_dialect.go @@ -44,7 +44,7 @@ func (db *postgres) SqlType(c *core.Column) string { return "timestamp with time zone" case core.Float: res = core.Real - case core.TinyText, core.MediumText, core.LongText: + case core.TinyText, core.MediumText, core.LongText, core.Uuid: res = core.Text case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: return core.Bytea @@ -142,86 +142,96 @@ func (db *postgres) IsColumnExist(tableName string, col *core.Column) (bool, err } func (db *postgres) 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 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + 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 AND f.attnum > 0 ORDER BY f.attnum;` - rows, err := db.DB().Query(s, args...) - if err != nil { - return nil, nil, err - } - defer rows.Close() + 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) + cols := make(map[string]*core.Column) + colSeq := make([]string, 0) - for rows.Next() { - col := new(core.Column) - col.Indexes = make(map[string]bool) + for rows.Next() { + col := new(core.Column) + col.Indexes = make(map[string]bool) - var colName, isNullable, dataType string - var maxLenStr, colDefault, numPrecision, numRadix *string - err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &numPrecision, &numRadix) - if err != nil { - return nil, nil, err - } + 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 + } + } - var maxLen int - if maxLenStr != nil { - maxLen, err = strconv.Atoi(*maxLenStr) - if err != nil { - return nil, nil, err - } - } + col.Name = strings.Trim(colName, `" `) - col.Name = strings.Trim(colName, `" `) + if colDefault != nil || isPK { + if isPK { + col.IsPrimaryKey = true + } else { + col.Default = *colDefault + } + } - if colDefault != nil { - if strings.HasPrefix(*colDefault, "nextval") { - col.IsPrimaryKey = true - } else { - col.Default = *colDefault - } - } + col.Nullable = (isNullable == "YES") - col.Nullable = (isNullable == "YES") + switch dataType { + case "character varying", "character": + col.SQLType = core.SQLType{core.Varchar, 0, 0} + case "timestamp without time zone": + col.SQLType = core.SQLType{core.DateTime, 0, 0} + case "timestamp with time zone": + col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + case "double precision": + col.SQLType = core.SQLType{core.Double, 0, 0} + case "boolean": + col.SQLType = core.SQLType{core.Bool, 0, 0} + case "time without time zone": + col.SQLType = core.SQLType{core.Time, 0, 0} + default: + col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} + } + if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) + } - switch dataType { - case "character varying", "character": - col.SQLType = core.SQLType{core.Varchar, 0, 0} - case "timestamp without time zone": - col.SQLType = core.SQLType{core.DateTime, 0, 0} - case "timestamp with time zone": - col.SQLType = core.SQLType{core.TimeStampz, 0, 0} - case "double precision": - col.SQLType = core.SQLType{core.Double, 0, 0} - case "boolean": - col.SQLType = core.SQLType{core.Bool, 0, 0} - case "time without time zone": - col.SQLType = core.SQLType{core.Time, 0, 0} - default: - col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} - } - if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) - } + col.Length = maxLen - col.Length = maxLen + if col.SQLType.IsText() { + if col.Default != "" { + col.Default = "'"+col.Default+"'" + } else { + if col.DefaultIsEmpty { + col.Default = "''" + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } - if col.SQLType.IsText() { - 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 + return colSeq, cols, nil } func (db *postgres) GetTables() ([]*core.Table, error) { From c039d40ada322aecf54fe0a611cc11cd28d8f755 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Jun 2014 17:09:49 +0800 Subject: [PATCH 02/15] bug fixed for joinstr when has two Join --- session.go | 3 ++- statement.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/session.go b/session.go index b1636f4e..ea7ae3c6 100644 --- a/session.go +++ b/session.go @@ -389,7 +389,8 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b for key, data := range objMap { if col = table.GetColumn(key); col == nil { - session.Engine.LogWarn(fmt.Sprintf("table %v's has not column %v. %v", table.Name, key, table.Columns())) + session.Engine.LogWarn(fmt.Sprintf("struct %v's has not field %v. %v", + table.Type.Name(), key, table.ColumnsSeq())) continue } diff --git a/statement.go b/statement.go index 33aa20a7..28b0d0dc 100644 --- a/statement.go +++ b/statement.go @@ -833,7 +833,7 @@ func (statement *Statement) OrderBy(order string) *Statement { //The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN func (statement *Statement) Join(join_operator, tablename, condition string) *Statement { if statement.JoinStr != "" { - statement.JoinStr = statement.JoinStr + fmt.Sprintf("%v JOIN %v ON %v", join_operator, tablename, condition) + statement.JoinStr = statement.JoinStr + fmt.Sprintf(" %v JOIN %v ON %v", join_operator, tablename, condition) } else { statement.JoinStr = fmt.Sprintf("%v JOIN %v ON %v", join_operator, tablename, condition) } From 207db2f97d74a3689c6c10b1258417acdd09408c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 26 Jun 2014 14:35:40 +0800 Subject: [PATCH 03/15] postgres uuid bug fixed --- postgres_dialect.go | 142 ++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/postgres_dialect.go b/postgres_dialect.go index ff5216c6..61d4f1e2 100644 --- a/postgres_dialect.go +++ b/postgres_dialect.go @@ -44,8 +44,10 @@ func (db *postgres) SqlType(c *core.Column) string { return "timestamp with time zone" case core.Float: res = core.Real - case core.TinyText, core.MediumText, core.LongText, core.Uuid: + case core.TinyText, core.MediumText, core.LongText: res = core.Text + case core.Uuid: + res = core.Uuid case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: return core.Bytea case core.Double: @@ -142,8 +144,8 @@ func (db *postgres) IsColumnExist(tableName string, col *core.Column) (bool, err } func (db *postgres) 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 , + 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 @@ -155,83 +157,83 @@ FROM pg_attribute f 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 AND f.attnum > 0 ORDER BY f.attnum;` - rows, err := db.DB().Query(s, args...) - if err != nil { - return nil, nil, err - } - defer rows.Close() + 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) + cols := make(map[string]*core.Column) + colSeq := make([]string, 0) - for rows.Next() { - col := new(core.Column) - col.Indexes = make(map[string]bool) + for rows.Next() { + col := new(core.Column) + col.Indexes = make(map[string]bool) - 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 - } - } + 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, `" `) + col.Name = strings.Trim(colName, `" `) - if colDefault != nil || isPK { - if isPK { - col.IsPrimaryKey = true - } else { - col.Default = *colDefault - } - } + if colDefault != nil || isPK { + if isPK { + col.IsPrimaryKey = true + } else { + col.Default = *colDefault + } + } - col.Nullable = (isNullable == "YES") + col.Nullable = (isNullable == "YES") - switch dataType { - case "character varying", "character": - col.SQLType = core.SQLType{core.Varchar, 0, 0} - case "timestamp without time zone": - col.SQLType = core.SQLType{core.DateTime, 0, 0} - case "timestamp with time zone": - col.SQLType = core.SQLType{core.TimeStampz, 0, 0} - case "double precision": - col.SQLType = core.SQLType{core.Double, 0, 0} - case "boolean": - col.SQLType = core.SQLType{core.Bool, 0, 0} - case "time without time zone": - col.SQLType = core.SQLType{core.Time, 0, 0} - default: - col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} - } - if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) - } + switch dataType { + case "character varying", "character": + col.SQLType = core.SQLType{core.Varchar, 0, 0} + case "timestamp without time zone": + col.SQLType = core.SQLType{core.DateTime, 0, 0} + case "timestamp with time zone": + col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + case "double precision": + col.SQLType = core.SQLType{core.Double, 0, 0} + case "boolean": + col.SQLType = core.SQLType{core.Bool, 0, 0} + case "time without time zone": + col.SQLType = core.SQLType{core.Time, 0, 0} + default: + col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} + } + if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) + } - col.Length = maxLen + col.Length = maxLen - if col.SQLType.IsText() { - if col.Default != "" { - col.Default = "'"+col.Default+"'" - } else { - if col.DefaultIsEmpty { - col.Default = "''" - } - } - } - cols[col.Name] = col - colSeq = append(colSeq, col.Name) - } + if col.SQLType.IsText() { + 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 + return colSeq, cols, nil } func (db *postgres) GetTables() ([]*core.Table, error) { From fdb3b3c4254afe840eb348c091a0d1c724a4e661 Mon Sep 17 00:00:00 2001 From: wulove Date: Tue, 1 Jul 2014 10:05:22 +0800 Subject: [PATCH 04/15] Update xorm.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加对https://github.com/denisenkom/go-mssqldb的适配,go-mssqldb对mssql数据库兼容支持较odbc好 --- xorm.go | 1 + 1 file changed, 1 insertion(+) diff --git a/xorm.go b/xorm.go index 86a4ee55..b82d967f 100644 --- a/xorm.go +++ b/xorm.go @@ -29,6 +29,7 @@ func regDrvsNDialects() bool { getDriver func() core.Driver getDialect func() core.Dialect }{ + "mssql": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, "odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, From 6dd8bcfca00cc62defa17157229a5441ddaec5f8 Mon Sep 17 00:00:00 2001 From: wulove Date: Tue, 1 Jul 2014 10:09:16 +0800 Subject: [PATCH 05/15] Update engine.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当数据库driver是https://github.com/denisenkom/go-mssqldb时,不对datetime类型进行时间截取,在go-mssqldb中会自动处理 --- engine.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine.go b/engine.go index 2bd8d523..b17b006d 100644 --- a/engine.go +++ b/engine.go @@ -1383,6 +1383,8 @@ func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{} case core.TimeStampz: if engine.dialect.DBType() == core.MSSQL { v = engine.TZTime(t).Format("2006-01-02T15:04:05.9999999Z07:00") + } else if engine.DriverName() == "mssql" { + v = engine.TZTime(t) } else { v = engine.TZTime(t).Format(time.RFC3339Nano) } From 917d7d0fcec83be39d8af035b513cf43436d7681 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 1 Jul 2014 15:38:05 +0800 Subject: [PATCH 06/15] add mssql driver support --- README.md | 4 ++++ README_CN.md | 2 ++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 730f890a..8bea364f 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,12 @@ Drivers for Go's sql package which currently support database/sql includes: * Postgres: [github.com/lib/pq](https://github.com/lib/pq) +* MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) + * MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc) + + # Changelog * **v0.4.0 RC1** diff --git a/README_CN.md b/README_CN.md index f3974aa3..79875a5e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -38,6 +38,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * Postgres: [github.com/lib/pq](https://github.com/lib/pq) +* MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) + * MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc) ## 更新日志 From 560525e3cae3d2caa10b4cb36e3c3024e0a7ef4f Mon Sep 17 00:00:00 2001 From: Eryx Date: Thu, 3 Jul 2014 23:35:31 +0800 Subject: [PATCH 07/15] bugfix: getting the right column lines when the culumn type is varchar (n) or number (n, m) --- sqlite3_dialect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite3_dialect.go b/sqlite3_dialect.go index 0626cf4e..ddf6a5f2 100644 --- a/sqlite3_dialect.go +++ b/sqlite3_dialect.go @@ -129,7 +129,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu } nStart := strings.Index(name, "(") - nEnd := strings.Index(name, ")") + nEnd := strings.LastIndex(name, ")") colCreates := strings.Split(name[nStart+1:nEnd], ",") cols := make(map[string]*core.Column) colSeq := make([]string, 0) From a404099f257cade8c40c882715b67eb3571fd530 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 4 Jul 2014 14:16:03 +0800 Subject: [PATCH 08/15] improved docs --- docs/QuickStart.md | 2 +- docs/QuickStartCN.md | 2 +- xorm.go | 56 +++++++++++++++++--------------------------- 3 files changed, 23 insertions(+), 37 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 47aa98dc..6f568071 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -91,7 +91,7 @@ f, err := os.Create("sql.log") println(err.Error()) return } -engine.Logger = f +engine.Logger = xorm.NewSimpleLogger(f) ``` 3.Engine provide DB connection pool settings. diff --git a/docs/QuickStartCN.md b/docs/QuickStartCN.md index 366a6cad..d0fd45b1 100644 --- a/docs/QuickStartCN.md +++ b/docs/QuickStartCN.md @@ -95,7 +95,7 @@ f, err := os.Create("sql.log") println(err.Error()) return } -engine.Logger = f +engine.Logger = xorm.NewSimpleLogger(f) ``` 3.engine内部支持连接池接口。 diff --git a/xorm.go b/xorm.go index b82d967f..bf4aa8f6 100644 --- a/xorm.go +++ b/xorm.go @@ -1,7 +1,6 @@ package xorm import ( - "database/sql" "errors" "fmt" "os" @@ -17,42 +16,29 @@ const ( Version string = "0.4" ) -// !nashtsai! implicit register drivers and dialects is no good, as init() can be called before sql driver got registered -// func init() { -// regDrvsNDialects() -// } - func regDrvsNDialects() bool { - if core.RegisteredDriverSize() == 0 { - providedDrvsNDialects := map[string]struct { - dbType core.DbType - getDriver func() core.Driver - getDialect func() core.Dialect - }{ - "mssql": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, - "odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access - "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, - "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, - "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, - "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, - "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, - "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }}, - } - - for driverName, v := range providedDrvsNDialects { - _, err := sql.Open(driverName, "") - if err == nil { - // fmt.Printf("driver succeed: %v\n", driverName) - core.RegisterDriver(driverName, v.getDriver()) - core.RegisterDialect(v.dbType, v.getDialect()) - } else { - // fmt.Printf("driver failed: %v | err: %v\n", driverName, err) - } - } - return true - } else { - return false + providedDrvsNDialects := map[string]struct { + dbType core.DbType + getDriver func() core.Driver + getDialect func() core.Dialect + }{ + "mssql": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, + "odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access + "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, + "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, + "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, + "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, + "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, + "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }}, } + + for driverName, v := range providedDrvsNDialects { + if driver := core.QueryDriver(driverName); driver == nil { + core.RegisterDriver(driverName, v.getDriver()) + core.RegisterDialect(v.dbType, v.getDialect()) + } + } + return true } func close(engine *Engine) { From 12a68f628a3f41d4851eadb7a889836fbb56305b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Jul 2014 09:34:56 +0800 Subject: [PATCH 09/15] bug fixed #139 --- pq_driver.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/pq_driver.go b/pq_driver.go index a5bb6718..c8dd5aa0 100644 --- a/pq_driver.go +++ b/pq_driver.go @@ -3,6 +3,8 @@ package xorm import ( "errors" "fmt" + "net/url" + "sort" "strings" "github.com/go-xorm/core" @@ -29,6 +31,53 @@ func errorf(s string, args ...interface{}) { panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) } +func parseURL(connstr string) (string, error) { + u, err := url.Parse(connstr) + if err != nil { + return "", err + } + + if u.Scheme != "postgres" { + return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme) + } + + var kvs []string + escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) + accrue := func(k, v string) { + if v != "" { + kvs = append(kvs, k+"="+escaper.Replace(v)) + } + } + + if u.User != nil { + v := u.User.Username() + accrue("user", v) + + v, _ = u.User.Password() + accrue("password", v) + } + + i := strings.Index(u.Host, ":") + if i < 0 { + accrue("host", u.Host) + } else { + accrue("host", u.Host[:i]) + accrue("port", u.Host[i+1:]) + } + + if u.Path != "" { + accrue("dbname", u.Path[1:]) + } + + q := u.Query() + for k := range q { + accrue(k, q.Get(k)) + } + + sort.Strings(kvs) // Makes testing easier (not a performance concern) + return strings.Join(kvs, " "), nil +} + func parseOpts(name string, o values) { if len(name) == 0 { return @@ -49,6 +98,13 @@ func parseOpts(name string, o values) { func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { db := &core.Uri{DbType: core.POSTGRES} o := make(values) + var err error + if strings.HasPrefix(dataSourceName, "postgres://") { + dataSourceName, err = parseURL(dataSourceName) + if err != nil { + return nil, err + } + } parseOpts(dataSourceName, o) db.DbName = o.Get("dbname") From f7a32eed681360752681a1b759bfe6dc18df85b1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Jul 2014 20:36:26 +0800 Subject: [PATCH 10/15] online build status --- README.md | 2 +- README_CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8bea364f..5cb7eab6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Xorm is a simple and powerful ORM for Go. -[![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lunny/xorm/trend.png)](https://bitdeli.com/free "Bitdeli Badge") +[![Build Status](https://drone.io/github.com/go-xorm/tests/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lunny/xorm/trend.png)](https://bitdeli.com/free "Bitdeli Badge") # Features diff --git a/README_CN.md b/README_CN.md index 79875a5e..8aee186a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,7 +4,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 -[![Build Status](https://drone.io/github.com/go-xorm/xorm/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) +[![Build Status](https://drone.io/github.com/go-xorm/tests/status.png)](https://drone.io/github.com/go-xorm/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) ## 特性 From 244c0989f2a6ab87ffb0f127da7f3f8bf5cfa7c8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 9 Jul 2014 21:48:48 +0800 Subject: [PATCH 11/15] working on #140 --- session.go | 69 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/session.go b/session.go index ea7ae3c6..222d398c 100644 --- a/session.go +++ b/session.go @@ -39,7 +39,8 @@ type Session struct { beforeClosures []func(interface{}) afterClosures []func(interface{}) - stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) + stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) + cascadeDeep int } // Method Init reset the session as the init status. @@ -896,25 +897,23 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { rows, err := session.Rows(bean) if err != nil { return err - } else { - defer rows.Close() - //b := reflect.New(iterator.beanType).Interface() - i := 0 - for rows.Next() { - b := reflect.New(rows.beanType).Interface() - err = rows.Scan(b) - if err != nil { - return err - } - err = fun(i, b) - if err != nil { - return err - } - i++ - } - return err } - return nil + defer rows.Close() + //b := reflect.New(iterator.beanType).Interface() + i := 0 + for rows.Next() { + b := reflect.New(rows.beanType).Interface() + err = rows.Scan(b) + if err != nil { + return err + } + err = fun(i, b) + if err != nil { + return err + } + i++ + } + return err } func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { @@ -2452,6 +2451,38 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x)) default: + if fieldType.Elem().Kind() == reflect.Struct { + if session.Statement.UseCascade { + structInter := reflect.New(fieldType.Elem()) + fmt.Println(structInter, fieldType.Elem()) + table := session.Engine.autoMapType(structInter.Elem()) + if table != nil { + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + if x != 0 { + // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch + // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne + // property to be fetched lazily + newsession := session.Engine.NewSession() + defer newsession.Close() + has, err := newsession.Id(x).Get(structInter.Interface()) + if err != nil { + return err + } + if has { + v = structInter.Interface() + fieldValue.Set(reflect.ValueOf(v)) + } else { + return errors.New("cascade obj is not exist!") + } + } + } + } else { + return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String()) + } + } return fmt.Errorf("unsupported type in Scan: %s", reflect.TypeOf(v).String()) } default: From c9db8e93f7c2c5df8050cb0da244377a89f8877f Mon Sep 17 00:00:00 2001 From: Nicolas Duval Date: Fri, 11 Jul 2014 10:40:41 -0700 Subject: [PATCH 12/15] Support uint64 with high bit set. This limitation is due to "database/sql" go package implementation. --- session.go | 2 ++ statement.go | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/session.go b/session.go index 222d398c..f4394abc 100644 --- a/session.go +++ b/session.go @@ -2597,6 +2597,8 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val } else { return nil, ErrUnSupportedType } + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return int64(fieldValue.Uint()), nil default: return fieldValue.Interface(), nil } diff --git a/statement.go b/statement.go index 28b0d0dc..d872e267 100644 --- a/statement.go +++ b/statement.go @@ -375,7 +375,8 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if !requiredField && fieldValue.Uint() == 0 { continue } - val = fieldValue.Interface() + t := int64(fieldValue.Uint()) + val = reflect.ValueOf(&t).Interface() case reflect.Struct: if fieldType == reflect.TypeOf(time.Now()) { t := fieldValue.Interface().(time.Time) @@ -546,7 +547,8 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, if !requiredField && fieldValue.Uint() == 0 { continue } - val = fieldValue.Interface() + t := int64(fieldValue.Uint()) + val = reflect.ValueOf(&t).Interface() case reflect.Struct: if fieldType == reflect.TypeOf(time.Now()) { t := fieldValue.Interface().(time.Time) From 3c457a8cf0e74a4dcea497f80817bbfbe73b37a4 Mon Sep 17 00:00:00 2001 From: kevinGunn Date: Tue, 15 Jul 2014 23:25:24 +0800 Subject: [PATCH 13/15] add mothod Decr --- engine.go | 7 +++++++ session.go | 13 +++++++++++++ statement.go | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/engine.go b/engine.go index b17b006d..caf1dbe4 100644 --- a/engine.go +++ b/engine.go @@ -461,6 +461,13 @@ func (engine *Engine) Incr(column string, arg ...interface{}) *Session { return session.Incr(column, arg...) } +// Method Inc provides a update string like "column = column - ?" +func (engine *Engine) Decr(column string, arg ...interface{}) *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.Decr(column, arg...) +} + // Temporarily change the Get, Find, Update's table func (engine *Engine) Table(tableNameOrBean interface{}) *Session { session := engine.NewSession() diff --git a/session.go b/session.go index f4394abc..a84f6429 100644 --- a/session.go +++ b/session.go @@ -146,6 +146,12 @@ func (session *Session) Incr(column string, arg ...interface{}) *Session { return session } +// Method In provides a query string like "count = count - 1" +func (session *Session) Decr(column string, arg ...interface{}) *Session { + session.Statement.Decr(column, arg...) + return session +} + // Method Cols provides some columns to special func (session *Session) Cols(columns ...string) *Session { session.Statement.Cols(columns...) @@ -3043,6 +3049,13 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" + ?") args = append(args, v.arg) } + //for update action to like "column = column - ?" + decColumns := session.Statement.getDec() + for _, v := range decColumns { + colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" - ?") + args = append(args, v.arg) + } + var condiColNames []string var condiArgs []interface{} diff --git a/statement.go b/statement.go index d872e267..9286250d 100644 --- a/statement.go +++ b/statement.go @@ -20,6 +20,11 @@ type incrParam struct { arg interface{} } +type decrParam struct { + colName string + arg interface{} +} + // statement save all the sql info for executing SQL type Statement struct { RefTable *core.Table @@ -54,6 +59,7 @@ type Statement struct { mustColumnMap map[string]bool inColumns map[string]*inParam incrColumns map[string]incrParam + decrColumns map[string]decrParam } // init @@ -85,6 +91,7 @@ func (statement *Statement) Init() { statement.checkVersion = true statement.inColumns = make(map[string]*inParam) statement.incrColumns = make(map[string]incrParam) + statement.decrColumns = make(map[string]decrParam) } // add the raw sql statement @@ -676,11 +683,27 @@ func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { return statement } +// Generate "Update ... Set column = column - arg" statment +func (statement *Statement) Decr(column string, arg ...interface{}) *Statement { + k := strings.ToLower(column) + if len(arg) > 0 { + statement.decrColumns[k] = decrParam{column, arg[0]} + } else { + statement.decrColumns[k] = decrParam{column, 1} + } + return statement +} + // Generate "Update ... Set column = column + arg" statment func (statement *Statement) getInc() map[string]incrParam { return statement.incrColumns } +// Generate "Update ... Set column = column - arg" statment +func (statement *Statement) getDec() map[string]decrParam { + return statement.decrColumns +} + // Generate "Where column IN (?) " statment func (statement *Statement) In(column string, args ...interface{}) *Statement { k := strings.ToLower(column) From f040283a1f33ec0fae1ea3c12bcd9706636bc26e Mon Sep 17 00:00:00 2001 From: kevinGunn Date: Tue, 15 Jul 2014 23:32:20 +0800 Subject: [PATCH 14/15] add mothod Decr --- engine.go | 2 +- session.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine.go b/engine.go index caf1dbe4..32924e9a 100644 --- a/engine.go +++ b/engine.go @@ -461,7 +461,7 @@ func (engine *Engine) Incr(column string, arg ...interface{}) *Session { return session.Incr(column, arg...) } -// Method Inc provides a update string like "column = column - ?" +// Method Decr provides a update string like "column = column - ?" func (engine *Engine) Decr(column string, arg ...interface{}) *Session { session := engine.NewSession() session.IsAutoClose = true diff --git a/session.go b/session.go index a84f6429..f0a345b2 100644 --- a/session.go +++ b/session.go @@ -146,7 +146,7 @@ func (session *Session) Incr(column string, arg ...interface{}) *Session { return session } -// Method In provides a query string like "count = count - 1" +// Method Decr provides a query string like "count = count - 1" func (session *Session) Decr(column string, arg ...interface{}) *Session { session.Statement.Decr(column, arg...) return session From 4a03e015f4aa03f599fc554aebbd0bd0041d20a4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 19 Jul 2014 18:02:02 +0800 Subject: [PATCH 15/15] bug fixed for log --- engine.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/engine.go b/engine.go index 32924e9a..21e1082e 100644 --- a/engine.go +++ b/engine.go @@ -1070,21 +1070,21 @@ func (engine *Engine) Sync2(beans ...interface{}) error { if engine.dialect.DBType() == core.MYSQL { _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) } else { - engine.LogWarn("Table %s Column %s Old data type is %s, new data type is %s", - table.Name, col.Name, oriCol.SQLType.Name, col.SQLType.Name) + engine.LogWarn(fmt.Sprintf("Table %s Column %s db type is %s, struct type is %s\n", + table.Name, col.Name, oriCol.SQLType.Name, col.SQLType.Name)) } } else { - engine.LogWarn("Table %s Column %s Old data type is %s, new data type is %s", - table.Name, col.Name, oriCol.SQLType.Name, col.SQLType.Name) + engine.LogWarn(fmt.Sprintf("Table %s Column %s db type is %s, struct type is %s", + table.Name, col.Name, oriCol.SQLType.Name, col.SQLType.Name)) } } if col.Default != oriCol.Default { - engine.LogWarn("Table %s Column %s Old default is %s, new default is %s", - table.Name, col.Name, oriCol.Default, col.Default) + engine.LogWarn(fmt.Sprintf("Table %s Column %s db default is %s, struct default is %s", + table.Name, col.Name, oriCol.Default, col.Default)) } if col.Nullable != oriCol.Nullable { - engine.LogWarn("Table %s Column %s Old nullable is %v, new nullable is %v", - table.Name, col.Name, oriCol.Nullable, col.Nullable) + engine.LogWarn(fmt.Sprintf("Table %s Column %s db nullable is %v, struct nullable is %v", + table.Name, col.Name, oriCol.Nullable, col.Nullable)) } } else { session := engine.NewSession()