From 5f3abae1163df211bea94958942ee37684e427ce Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 29 Nov 2013 13:39:20 +0800 Subject: [PATCH 1/4] changelog --- docs/Changelog.md | 1 + docs/ChangelogCN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index b221c4a9..bc737e68 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,6 @@ ## Changelog +* **v0.2.3** : Improved documents; Optimistic Locking support; Timestamp with time zone support; Mapper change to tableMapper and columnMapper & added PrefixMapper & SuffixMapper support custom table or column name's prefix and suffix;Insert now return affected, err instead of id, err; Added UseBool & Distinct; * **v0.2.2** : Postgres drivers now support lib/pq; Added method Iterate for record by record to handler;Added SetMaxConns(go1.2+) support; some bugs fixed. * **v0.2.1** : Added database reverse tool, now support generate go & c++ codes, see [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); some bug fixed. * **v0.2.0** : Added Cache supported, select is speeder up 3~5x; Added SameMapper for same name between struct and table; Added Sync method for auto added tables, columns, indexes; diff --git a/docs/ChangelogCN.md b/docs/ChangelogCN.md index f7fbdc61..19f6022d 100644 --- a/docs/ChangelogCN.md +++ b/docs/ChangelogCN.md @@ -1,5 +1,6 @@ ## 更新日志 +* **v0.2.3** : 改善了文档;提供了乐观锁支持;添加了带时区时间字段支持;Mapper现在分成表名Mapper和字段名Mapper,同时实现了表或字段的自定义前缀后缀;Insert方法的返回值含义从id, err更改为 affected, err,请大家注意;添加了UseBool 和 Distinct函数。 * **v0.2.2** : Postgres驱动新增了对lib/pq的支持;新增了逐条遍历方法Iterate;新增了SetMaxConns(go1.2+)支持,修复了bug若干; * **v0.2.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); 修复了一些bug. * **v0.2.0** : 新增 [缓存](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#120)支持,查询速度提升3-5倍; 新增数据库表和Struct同名的映射方式; 新增Sync同步表结构; From 6536f52ae0574a81fdcd2200362b29f217387747 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 30 Nov 2013 00:44:55 +0800 Subject: [PATCH 2/4] added first version of xorm shell --- xorm/install.sh | 20 -------- xorm/shell.go | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ xorm/xorm.go | 1 + 3 files changed, 122 insertions(+), 20 deletions(-) delete mode 100755 xorm/install.sh create mode 100644 xorm/shell.go diff --git a/xorm/install.sh b/xorm/install.sh deleted file mode 100755 index e8455d2a..00000000 --- a/xorm/install.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -if [ ! -f install.sh ]; then -echo 'install must be run within its container folder' 1>&2 -exit 1 -fi - -CURDIR=`pwd` -NEWPATH="$GOPATH/src/github.com/lunny/xorm/${PWD##*/}" -if [ ! -d "$NEWPATH" ]; then -ln -s $CURDIR $NEWPATH -fi - -gofmt -w $CURDIR - -cd $NEWPATH -go install ${PWD##*/} -cd $CURDIR - -echo 'finished' diff --git a/xorm/shell.go b/xorm/shell.go new file mode 100644 index 00000000..7f4cb200 --- /dev/null +++ b/xorm/shell.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "github.com/lunny/xorm" + "strings" +) + +var CmdShell = &Command{ + UsageLine: "shell driverName datasourceName", + Short: "a general shell to operate all kinds of database", + Long: ` +general database's shell for sqlite3, mysql, postgres. + + driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres + datasourceName Database connection uri, for detail infomation please visit driver's project page +`, +} + +func init() { + CmdShell.Run = runShell + CmdShell.Flags = map[string]bool{} +} + +var engine *xorm.Engine + +func runShell(cmd *Command, args []string) { + if len(args) != 2 { + fmt.Println("params error, please see xorm help shell") + return + } + + var err error + engine, err = xorm.NewEngine(args[0], args[1]) + if err != nil { + fmt.Println(err) + return + } + + var scmd string + fmt.Print("xorm$ ") + for { + var input string + _, err := fmt.Scan(&input) + if err != nil { + fmt.Println(err) + continue + } + if strings.ToLower(input) == "exit" { + fmt.Println("bye") + return + } + if !strings.HasSuffix(input, ";") { + scmd = scmd + " " + input + continue + } + scmd = scmd + " " + input + lcmd := strings.TrimSpace(strings.ToLower(scmd)) + if strings.HasPrefix(lcmd, "select") { + res, err := engine.Query(scmd + "\n") + if err != nil { + fmt.Println(err) + } else { + if len(res) <= 0 { + fmt.Println("no records") + } else { + columns := make(map[string]int) + for k, _ := range res[0] { + columns[k] = len(k) + } + + for _, m := range res { + for k, s := range m { + l := len(string(s)) + if l > columns[k] { + columns[k] = l + } + } + } + + var maxlen = 0 + for _, l := range columns { + maxlen = maxlen + l + 3 + } + maxlen = maxlen + 1 + + fmt.Println(strings.Repeat("-", maxlen)) + fmt.Print("|") + slice := make([]string, 0) + for k, l := range columns { + fmt.Print(" " + k + " ") + fmt.Print(strings.Repeat(" ", l-len(k))) + fmt.Print("|") + slice = append(slice, k) + } + fmt.Print("\n") + for _, r := range res { + fmt.Print("|") + for _, k := range slice { + fmt.Print(" " + string(r[k]) + " ") + fmt.Print(strings.Repeat(" ", columns[k]-len(string(r[k])))) + fmt.Print("|") + } + fmt.Print("\n") + } + fmt.Println(strings.Repeat("-", maxlen)) + //fmt.Println(res) + } + } + } else { + cnt, err := engine.Exec(scmd) + if err != nil { + fmt.Println(err) + } else { + fmt.Printf("%d records changed.\n", cnt) + } + } + scmd = "" + fmt.Print("xorm$ ") + } +} diff --git a/xorm/xorm.go b/xorm/xorm.go index 78583a6a..e19933d5 100644 --- a/xorm/xorm.go +++ b/xorm/xorm.go @@ -22,6 +22,7 @@ const go11tag = true // The order here is the order in which they are printed by 'gopm help'. var commands = []*Command{ CmdReverse, + CmdShell, } func init() { From cf0ffb717fe7bbb65e09005862f34f67a8a9a8b0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 1 Dec 2013 11:08:17 +0800 Subject: [PATCH 3/4] use gopm as get & build tool --- README.md | 8 +++++++- README_CN.md | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 06082556..ad399a72 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,13 @@ Drivers for Go's sql package which currently support database/sql includes: [More changelogs ...](https://github.com/lunny/xorm/blob/master/docs/Changelog.md) -# Installation +# Installation + +If you have [gopm](https://github.com/gpmgo/gopm) installed, + + gopm get github.com/lunny/xorm + +Or go get github.com/lunny/xorm diff --git a/README_CN.md b/README_CN.md index b90c8118..b2fe1f9b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -50,6 +50,12 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 安装 +推荐使用 [gopm](https://github.com/gpmgo/gopm) 进行安装: + + gopm get github.com/lunny/xorm + +或者您也可以使用go工具进行安装: + go get github.com/lunny/xorm ## 文档 From 01e8ab4bcdadd0110f0849acf8ee46bf69180e70 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 2 Dec 2013 14:54:44 +0800 Subject: [PATCH 4/4] add created & updated tests --- base_test.go | 44 +++++++++++++++++++ statement.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/base_test.go b/base_test.go index 93c11d4c..d07e8950 100644 --- a/base_test.go +++ b/base_test.go @@ -1706,6 +1706,48 @@ func testPrefixTableName(engine *Engine, t *testing.T) { } } +type CreatedUpdated struct { + Id int64 + Name string + Value float64 `xorm:"numeric"` + Created time.Time `xorm:"created"` + Created2 time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` +} + +func testCreatedUpdated(engine *Engine, t *testing.T) { + err := engine.Sync(&CreatedUpdated{}) + if err != nil { + t.Error(err) + panic(err) + } + + c := &CreatedUpdated{Name: "test"} + _, err = engine.Insert(c) + if err != nil { + t.Error(err) + panic(err) + } + + c2 := new(CreatedUpdated) + has, err := engine.Id(c.Id).Get(c2) + if err != nil { + t.Error(err) + panic(err) + } + + if !has { + panic(errors.New("no id")) + } + + c2.Value -= 1 + _, err = engine.Id(c2.Id).Update(c2) + if err != nil { + t.Error(err) + panic(err) + } +} + func testAll(engine *Engine, t *testing.T) { fmt.Println("-------------- directCreateTable --------------") directCreateTable(engine, t) @@ -1800,6 +1842,8 @@ func testAll2(engine *Engine, t *testing.T) { testTime(engine, t) fmt.Println("-------------- testPrefixTableName --------------") testPrefixTableName(engine, t) + fmt.Println("-------------- testCreatedUpdated --------------") + testCreatedUpdated(engine, t) fmt.Println("-------------- transaction --------------") transaction(engine, t) } diff --git a/statement.go b/statement.go index 1dc06dc6..8400f0f8 100644 --- a/statement.go +++ b/statement.go @@ -116,6 +116,122 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { return statement } +func (statement *Statement) genFields(bean interface{}) map[string]interface{} { + results := make(map[string]interface{}) + table := statement.Engine.autoMap(bean) + for _, col := range table.Columns { + fieldValue := col.ValueOf(bean) + fieldType := reflect.TypeOf(fieldValue.Interface()) + var val interface{} + switch fieldType.Kind() { + case reflect.Bool: + if allUseBool { + val = fieldValue.Interface() + } else if _, ok := boolColumnMap[col.Name]; ok { + val = fieldValue.Interface() + } else { + // if a bool in a struct, it will not be as a condition because it default is false, + // please use Where() instead + continue + } + case reflect.String: + if fieldValue.String() == "" { + continue + } + // for MyString, should convert to string or panic + if fieldType.String() != reflect.String.String() { + val = fieldValue.String() + } else { + val = fieldValue.Interface() + } + case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64: + if fieldValue.Int() == 0 { + continue + } + val = fieldValue.Interface() + case reflect.Float32, reflect.Float64: + if fieldValue.Float() == 0.0 { + continue + } + val = fieldValue.Interface() + case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64: + if fieldValue.Uint() == 0 { + continue + } + val = fieldValue.Interface() + case reflect.Struct: + if fieldType == reflect.TypeOf(time.Now()) { + t := fieldValue.Interface().(time.Time) + if t.IsZero() || !fieldValue.IsValid() { + continue + } + var str string + if col.SQLType.Name == Time { + s := t.UTC().Format("2006-01-02 15:04:05") + val = s[11:19] + } else if col.SQLType.Name == Date { + str = t.Format("2006-01-02") + val = str + } else { + val = t + } + } else { + engine.autoMapType(fieldValue.Type()) + if table, ok := engine.Tables[fieldValue.Type()]; ok { + pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName) + if pkField.Int() != 0 { + val = pkField.Interface() + } else { + continue + } + } else { + val = fieldValue.Interface() + } + } + case reflect.Array, reflect.Slice, reflect.Map: + if fieldValue == reflect.Zero(fieldType) { + continue + } + if fieldValue.IsNil() || !fieldValue.IsValid() { + continue + } + + if col.SQLType.IsText() { + bytes, err := json.Marshal(fieldValue.Interface()) + if err != nil { + engine.LogSQL(err) + continue + } + val = string(bytes) + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) && + fieldType.Elem().Kind() == reflect.Uint8 { + if fieldValue.Len() > 0 { + val = fieldValue.Bytes() + } else { + continue + } + } else { + bytes, err = json.Marshal(fieldValue.Interface()) + if err != nil { + engine.LogSQL(err) + continue + } + val = bytes + } + } else { + continue + } + default: + val = fieldValue.Interface() + } + results[col.Name] = val + } + return results +} + // Auto generating conditions according a struct func buildConditions(engine *Engine, table *Table, bean interface{}, includeVersion bool, allUseBool bool, boolColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0)