├── VERSION ├── xorm ├── templates │ ├── c++ │ │ ├── config │ │ └── class.h.tpl │ ├── go │ │ ├── config │ │ └── struct.go.tpl │ └── goxorm │ │ ├── config │ │ └── struct.go.tpl ├── .gopmfile ├── lang.go ├── README.md ├── c++.go ├── cmd.go ├── xorm.go ├── shell.go ├── reverse.go └── go.go ├── benchmark.bat ├── benchmark.sh ├── .gopmfile ├── docs ├── cache_design.png ├── AutoMap.md ├── ChangelogCN.md ├── Changelog.md ├── COLUMNTYPE.md └── QuickStartEn.md ├── README.md ├── tests └── mysql_ddl.sql ├── .gitignore ├── error.go ├── examples ├── pool.go ├── singlemapping.go ├── derive.go ├── conversion.go ├── cache.go ├── sync.go ├── maxconnect.go ├── cachegoroutine.go └── goroutine.go ├── processors.go ├── filter.go ├── LICENSE ├── mymysql.go ├── CONTRIBUTING.md ├── xorm.go ├── README_CN.md ├── sqlite3_test.go ├── mssql_test.go ├── helpers.go ├── benchmark_base_test.go ├── mymysql_test.go ├── rows.go ├── doc.go ├── mapper.go ├── mysql_test.go ├── sqlite3.go ├── oracle.go ├── postgres_test.go ├── pool.go ├── mssql.go ├── postgres.go ├── mysql.go ├── cache.go └── table.go /VERSION: -------------------------------------------------------------------------------- 1 | xorm v0.3.1 2 | -------------------------------------------------------------------------------- /xorm/templates/c++/config: -------------------------------------------------------------------------------- 1 | lang=c++ -------------------------------------------------------------------------------- /xorm/templates/go/config: -------------------------------------------------------------------------------- 1 | lang=go -------------------------------------------------------------------------------- /benchmark.bat: -------------------------------------------------------------------------------- 1 | go test -v -bench=. -run=XXX -------------------------------------------------------------------------------- /benchmark.sh: -------------------------------------------------------------------------------- 1 | go test -v -bench=. -run=XXX -------------------------------------------------------------------------------- /.gopmfile: -------------------------------------------------------------------------------- 1 | [target] 2 | path = github.com/lunny/xorm -------------------------------------------------------------------------------- /xorm/.gopmfile: -------------------------------------------------------------------------------- 1 | [deps] 2 | github.com/lunny/xorm=../ -------------------------------------------------------------------------------- /xorm/templates/goxorm/config: -------------------------------------------------------------------------------- 1 | lang=go 2 | genJson=0 3 | prefix=cos_ 4 | -------------------------------------------------------------------------------- /docs/cache_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunny/xorm/HEAD/docs/cache_design.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xorm Has Moved! 2 | 3 | This repo is here as a legacy archive. Please use the [new Xorm repo](http://github.com/go-xorm/xorm). -------------------------------------------------------------------------------- /tests/mysql_ddl.sql: -------------------------------------------------------------------------------- 1 | --DROP DATABASE xorm_test; 2 | --DROP DATABASE xorm_test2; 3 | CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET utf8 COLLATE utf8_general_ci; 4 | CREATE DATABASE IF NOT EXISTS xorm_test2 CHARACTER SET utf8 COLLATE utf8_general_ci; 5 | -------------------------------------------------------------------------------- /xorm/templates/go/struct.go.tpl: -------------------------------------------------------------------------------- 1 | package {{.Model}} 2 | 3 | import ( 4 | {{range .Imports}}"{{.}}"{{end}} 5 | ) 6 | 7 | {{range .Tables}} 8 | type {{Mapper .Name}} struct { 9 | {{$table := .}} 10 | {{range .Columns}} {{Mapper .Name}} {{Type .}} 11 | {{end}} 12 | } 13 | 14 | {{end}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.db 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | vendor 25 | 26 | *.log 27 | .vendor 28 | temp_test.go 29 | -------------------------------------------------------------------------------- /xorm/templates/goxorm/struct.go.tpl: -------------------------------------------------------------------------------- 1 | package {{.Model}} 2 | 3 | {{$ilen := len .Imports}} 4 | {{if gt $ilen 0}} 5 | import ( 6 | {{range .Imports}}"{{.}}"{{end}} 7 | ) 8 | {{end}} 9 | 10 | {{range .Tables}} 11 | type {{Mapper .Name}} struct { 12 | {{$table := .}} 13 | {{$columns := .Columns}} 14 | {{range .ColumnsSeq}}{{$col := getCol $columns .}} {{Mapper $col.Name}} {{Type $col}} {{Tag $table $col}} 15 | {{end}} 16 | } 17 | 18 | {{end}} -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrParamsType error = errors.New("Params type error") 9 | ErrTableNotFound error = errors.New("Not found table") 10 | ErrUnSupportedType error = errors.New("Unsupported type error") 11 | ErrNotExist error = errors.New("Not exist error") 12 | ErrCacheFailed error = errors.New("Cache failed") 13 | ErrNeedDeletedCond error = errors.New("Delete need at least one condition") 14 | ErrNotImplemented error = errors.New("Not implemented.") 15 | ) 16 | -------------------------------------------------------------------------------- /xorm/templates/c++/class.h.tpl: -------------------------------------------------------------------------------- 1 | {{ range .Imports}} 2 | #include {{.}} 3 | {{ end }} 4 | 5 | {{range .Tables}}class {{Mapper .Name}} { 6 | {{$table := .}} 7 | public: 8 | {{range .Columns}}{{$name := Mapper .Name}} {{Type .}} Get{{Mapper .Name}}() { 9 | return this->m_{{UnTitle $name}}; 10 | } 11 | 12 | void Set{{$name}}({{Type .}} {{UnTitle $name}}) { 13 | this->m_{{UnTitle $name}} = {{UnTitle $name}}; 14 | } 15 | 16 | {{end}}private: 17 | {{range .Columns}}{{$name := Mapper .Name}} {{Type .}} m_{{UnTitle $name}}; 18 | {{end}} 19 | } 20 | 21 | {{end}} -------------------------------------------------------------------------------- /examples/pool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lunny/xorm" 6 | _ "github.com/mattn/go-sqlite3" 7 | "os" 8 | ) 9 | 10 | type User struct { 11 | Id int64 12 | Name string 13 | } 14 | 15 | func main() { 16 | f := "pool.db" 17 | os.Remove(f) 18 | 19 | Orm, err := NewEngine("sqlite3", f) 20 | if err != nil { 21 | fmt.Println(err) 22 | return 23 | } 24 | err = Orm.SetPool(NewSimpleConnectPool()) 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | 30 | Orm.ShowSQL = true 31 | err = Orm.CreateTables(&User{}) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | 37 | for i := 0; i < 10; i++ { 38 | _, err = Orm.Get(&User{}) 39 | if err != nil { 40 | fmt.Println(err) 41 | break 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /processors.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | // Executed before an object is initially persisted to the database 4 | type BeforeInsertProcessor interface { 5 | BeforeInsert() 6 | } 7 | 8 | // Executed before an object is updated 9 | type BeforeUpdateProcessor interface { 10 | BeforeUpdate() 11 | } 12 | 13 | // Executed before an object is deleted 14 | type BeforeDeleteProcessor interface { 15 | BeforeDelete() 16 | } 17 | 18 | // !nashtsai! TODO enable BeforeValidateProcessor when xorm start to support validations 19 | //// Executed before an object is validated 20 | //type BeforeValidateProcessor interface { 21 | // BeforeValidate() 22 | //} 23 | // -- 24 | 25 | // Executed after an object is persisted to the database 26 | type AfterInsertProcessor interface { 27 | AfterInsert() 28 | } 29 | 30 | // Executed after an object has been updated 31 | type AfterUpdateProcessor interface { 32 | AfterUpdate() 33 | } 34 | 35 | // Executed after an object has been deleted 36 | type AfterDeleteProcessor interface { 37 | AfterDelete() 38 | } 39 | -------------------------------------------------------------------------------- /examples/singlemapping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lunny/xorm" 6 | _ "github.com/mattn/go-sqlite3" 7 | "os" 8 | ) 9 | 10 | type User struct { 11 | Id int64 12 | Name string 13 | } 14 | 15 | type LoginInfo struct { 16 | Id int64 17 | IP string 18 | UserId int64 19 | // timestamp should be updated by database, so only allow get from db 20 | TimeStamp string `xorm:"<-"` 21 | // assume 22 | Nonuse int `xorm:"->"` 23 | } 24 | 25 | func main() { 26 | f := "singleMapping.db" 27 | os.Remove(f) 28 | 29 | Orm, err := NewEngine("sqlite3", f) 30 | if err != nil { 31 | fmt.Println(err) 32 | return 33 | } 34 | Orm.ShowSQL = true 35 | err = Orm.CreateTables(&User{}, &LoginInfo{}) 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | 41 | _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23}) 42 | if err != nil { 43 | fmt.Println(err) 44 | return 45 | } 46 | 47 | info := LoginInfo{} 48 | _, err = Orm.Id(1).Get(&info) 49 | if err != nil { 50 | fmt.Println(err) 51 | return 52 | } 53 | fmt.Println(info) 54 | } 55 | -------------------------------------------------------------------------------- /xorm/lang.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lunny/xorm" 5 | "io/ioutil" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | type LangTmpl struct { 11 | Funcs template.FuncMap 12 | Formater func(string) (string, error) 13 | GenImports func([]*xorm.Table) map[string]string 14 | } 15 | 16 | var ( 17 | mapper = &xorm.SnakeMapper{} 18 | langTmpls = map[string]LangTmpl{ 19 | "go": GoLangTmpl, 20 | "c++": CPlusTmpl, 21 | } 22 | ) 23 | 24 | func loadConfig(f string) map[string]string { 25 | bts, err := ioutil.ReadFile(f) 26 | if err != nil { 27 | return nil 28 | } 29 | configs := make(map[string]string) 30 | lines := strings.Split(string(bts), "\n") 31 | for _, line := range lines { 32 | line = strings.TrimRight(line, "\r") 33 | vs := strings.Split(line, "=") 34 | if len(vs) == 2 { 35 | configs[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1]) 36 | } 37 | } 38 | return configs 39 | } 40 | 41 | func unTitle(src string) string { 42 | if src == "" { 43 | return "" 44 | } 45 | 46 | if len(src) == 1 { 47 | return strings.ToLower(string(src[0])) 48 | } else { 49 | return strings.ToLower(string(src[0])) + src[1:] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/derive.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lunny/xorm" 6 | _ "github.com/mattn/go-sqlite3" 7 | "os" 8 | ) 9 | 10 | type User struct { 11 | Id int64 12 | Name string 13 | } 14 | 15 | type LoginInfo struct { 16 | Id int64 17 | IP string 18 | UserId int64 19 | } 20 | 21 | type LoginInfo1 struct { 22 | LoginInfo `xorm:"extends"` 23 | UserName string 24 | } 25 | 26 | func main() { 27 | f := "derive.db" 28 | os.Remove(f) 29 | 30 | Orm, err := NewEngine("sqlite3", f) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | defer Orm.Close() 36 | Orm.ShowSQL = true 37 | err = Orm.CreateTables(&User{}, &LoginInfo{}) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | 43 | _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1}) 44 | if err != nil { 45 | fmt.Println(err) 46 | return 47 | } 48 | 49 | info := LoginInfo{} 50 | _, err = Orm.Id(1).Get(&info) 51 | if err != nil { 52 | fmt.Println(err) 53 | return 54 | } 55 | fmt.Println(info) 56 | 57 | infos := make([]LoginInfo1, 0) 58 | err = Orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from 59 | login_info limit 10`).Find(&infos) 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | 65 | fmt.Println(infos) 66 | } 67 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Filter is an interface to filter SQL 9 | type Filter interface { 10 | Do(sql string, session *Session) string 11 | } 12 | 13 | // PgSeqFilter filter SQL replace ?, ? ... to $1, $2 ... 14 | type PgSeqFilter struct { 15 | } 16 | 17 | func (s *PgSeqFilter) Do(sql string, session *Session) string { 18 | segs := strings.Split(sql, "?") 19 | size := len(segs) 20 | res := "" 21 | for i, c := range segs { 22 | if i < size-1 { 23 | res += c + fmt.Sprintf("$%v", i+1) 24 | } 25 | } 26 | res += segs[size-1] 27 | return res 28 | } 29 | 30 | // QuoteFilter filter SQL replace ` to database's own quote character 31 | type QuoteFilter struct { 32 | } 33 | 34 | func (s *QuoteFilter) Do(sql string, session *Session) string { 35 | return strings.Replace(sql, "`", session.Engine.QuoteStr(), -1) 36 | } 37 | 38 | // IdFilter filter SQL replace (id) to primary key column name 39 | type IdFilter struct { 40 | } 41 | 42 | func (i *IdFilter) Do(sql string, session *Session) string { 43 | if session.Statement.RefTable != nil && len(session.Statement.RefTable.PrimaryKeys) == 1 { 44 | sql = strings.Replace(sql, "`(id)`", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1) 45 | sql = strings.Replace(sql, session.Engine.Quote("(id)"), session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1) 46 | return strings.Replace(sql, "(id)", session.Engine.Quote(session.Statement.RefTable.PrimaryKeys[0]), -1) 47 | } 48 | return sql 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 - 2014 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /docs/AutoMap.md: -------------------------------------------------------------------------------- 1 | When a struct auto mapping to a database's table, the below table describes how they change to each other: 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 61 | 64 | 65 |
go type's kind 6 | value methodxorm type 9 |
implemented ConversionConversion.ToDB / Conversion.FromDBText
int, int8, int16, int32, uint, uint8, uint16, uint32 Int
int64, uint64BigInt
float32Float
float64Double
complex64, complex128json.Marshal / json.UnMarshalVarchar(64)
[]uint8Blob
array, slice, map except []uint8json.Marshal / json.UnMarshalText
bool1 or 0Bool
stringVarchar(255)
time.TimeDateTime
cascade structprimary key field valueBigInt
structjson.Marshal / json.UnMarshalText
58 | Others 59 | 62 | Text 63 |
-------------------------------------------------------------------------------- /mymysql.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | type mymysql struct { 10 | mysql 11 | } 12 | 13 | type mymysqlParser struct { 14 | } 15 | 16 | func (p *mymysqlParser) parse(driverName, dataSourceName string) (*uri, error) { 17 | db := &uri{dbType: MYSQL} 18 | 19 | pd := strings.SplitN(dataSourceName, "*", 2) 20 | if len(pd) == 2 { 21 | // Parse protocol part of URI 22 | p := strings.SplitN(pd[0], ":", 2) 23 | if len(p) != 2 { 24 | return nil, errors.New("Wrong protocol part of URI") 25 | } 26 | db.proto = p[0] 27 | options := strings.Split(p[1], ",") 28 | db.raddr = options[0] 29 | for _, o := range options[1:] { 30 | kv := strings.SplitN(o, "=", 2) 31 | var k, v string 32 | if len(kv) == 2 { 33 | k, v = kv[0], kv[1] 34 | } else { 35 | k, v = o, "true" 36 | } 37 | switch k { 38 | case "laddr": 39 | db.laddr = v 40 | case "timeout": 41 | to, err := time.ParseDuration(v) 42 | if err != nil { 43 | return nil, err 44 | } 45 | db.timeout = to 46 | default: 47 | return nil, errors.New("Unknown option: " + k) 48 | } 49 | } 50 | // Remove protocol part 51 | pd = pd[1:] 52 | } 53 | // Parse database part of URI 54 | dup := strings.SplitN(pd[0], "/", 3) 55 | if len(dup) != 3 { 56 | return nil, errors.New("Wrong database part of URI") 57 | } 58 | db.dbName = dup[0] 59 | db.user = dup[1] 60 | db.passwd = dup[2] 61 | 62 | return db, nil 63 | } 64 | 65 | func (db *mymysql) Init(drivername, uri string) error { 66 | return db.mysql.base.init(&mymysqlParser{}, drivername, uri) 67 | } 68 | -------------------------------------------------------------------------------- /xorm/README.md: -------------------------------------------------------------------------------- 1 | # xorm tools 2 | 3 | 4 | xorm tools is a set of tools for database operation. 5 | 6 | ## Install 7 | 8 | `go get github.com/lunny/xorm/xorm` 9 | 10 | and you should install the depends below: 11 | 12 | * github.com/lunny/xorm 13 | 14 | * Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) 15 | 16 | * MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) 17 | 18 | * SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) 19 | 20 | * Postgres: [github.com/bylevel/pq](https://github.com/bylevel/pq) 21 | 22 | 23 | ## Reverse 24 | 25 | After you installed the tool, you can type 26 | 27 | `xorm help reverse` 28 | 29 | to get help 30 | 31 | example: 32 | 33 | sqlite: 34 | `xorm reverse sqite3 test.db templates/goxorm` 35 | 36 | mysql: 37 | `xorm reverse mysql root:@/xorm_test?charset=utf8 templates/goxorm` 38 | 39 | mymysql: 40 | `xorm reverse mymysql xorm_test2/root/ templates/goxorm` 41 | 42 | postgres: 43 | `xorm reverse postgres "dbname=xorm_test sslmode=disable" templates/goxorm` 44 | 45 | will generated go files in `./model` directory 46 | 47 | ## Template and Config 48 | 49 | Now, xorm tool supports go and c++ two languages and have go, goxorm, c++ three of default templates. In template directory, we can put a config file to control how to generating. 50 | 51 | ```` 52 | lang=go 53 | genJson=1 54 | ``` 55 | 56 | lang must be go or c++ now. 57 | genJson can be 1 or 0, if 1 then the struct will have json tag. 58 | 59 | ## LICENSE 60 | 61 | BSD License 62 | [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) 63 | -------------------------------------------------------------------------------- /examples/conversion.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/lunny/xorm" 7 | _ "github.com/mattn/go-sqlite3" 8 | "os" 9 | ) 10 | 11 | type Status struct { 12 | Name string 13 | Color string 14 | } 15 | 16 | var ( 17 | Registed Status = Status{"Registed", "white"} 18 | Approved Status = Status{"Approved", "green"} 19 | Removed Status = Status{"Removed", "red"} 20 | Statuses map[string]Status = map[string]Status{ 21 | Registed.Name: Registed, 22 | Approved.Name: Approved, 23 | Removed.Name: Removed, 24 | } 25 | ) 26 | 27 | func (s *Status) FromDB(bytes []byte) error { 28 | if r, ok := Statuses[string(bytes)]; ok { 29 | *s = r 30 | return nil 31 | } else { 32 | return errors.New("no this data") 33 | } 34 | } 35 | 36 | func (s *Status) ToDB() ([]byte, error) { 37 | return []byte(s.Name), nil 38 | } 39 | 40 | type User struct { 41 | Id int64 42 | Name string 43 | Status Status `xorm:"varchar(40)"` 44 | } 45 | 46 | func main() { 47 | f := "conversion.db" 48 | os.Remove(f) 49 | 50 | Orm, err := NewEngine("sqlite3", f) 51 | if err != nil { 52 | fmt.Println(err) 53 | return 54 | } 55 | Orm.ShowSQL = true 56 | err = Orm.CreateTables(&User{}) 57 | if err != nil { 58 | fmt.Println(err) 59 | return 60 | } 61 | 62 | _, err = Orm.Insert(&User{1, "xlw", Registed}) 63 | if err != nil { 64 | fmt.Println(err) 65 | return 66 | } 67 | 68 | users := make([]User, 0) 69 | err = Orm.Find(&users) 70 | if err != nil { 71 | fmt.Println(err) 72 | return 73 | } 74 | 75 | fmt.Println(users) 76 | } 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to xorm 2 | 3 | `xorm` has a backlog of [pull requests](https://help.github.com/articles/using-pull-requests), but contributions are still very 4 | much welcome. You can help with patch review, submitting bug reports, 5 | or adding new functionality. There is no formal style guide, but 6 | please conform to the style of existing code and general Go formatting 7 | conventions when submitting patches. 8 | 9 | * [fork a repo](https://help.github.com/articles/fork-a-repo) 10 | * [creating a pull request ](https://help.github.com/articles/creating-a-pull-request) 11 | 12 | ### Patch review 13 | 14 | Help review existing open [pull requests](https://help.github.com/articles/using-pull-requests) by commenting on the code or 15 | proposed functionality. 16 | 17 | ### Bug reports 18 | 19 | We appreciate any bug reports, but especially ones with self-contained 20 | (doesn't depend on code outside of pq), minimal (can't be simplified 21 | further) test cases. It's especially helpful if you can submit a pull 22 | request with just the failing test case (you'll probably want to 23 | pattern it after the tests in 24 | [base_test.go](https://github.com/lunny/xorm/blob/master/base_test.go) AND 25 | [benchmark_base_test.go](https://github.com/lunny/xorm/blob/master/benchmark_base_test.go). 26 | 27 | If you implements a new database interface, you maybe need to add a _test.go file. 28 | For example, [mysql_test.go](https://github.com/lunny/xorm/blob/master/mysql_test.go) 29 | 30 | ### New functionality 31 | 32 | There are a number of pending patches for new functionality, so 33 | additional feature patches will take a while to merge. Still, patches 34 | are generally reviewed based on usefulness and complexity in addition 35 | to time-in-queue, so if you have a knockout idea, take a shot. Feel 36 | free to open an issue discussion your proposed patch beforehand. 37 | -------------------------------------------------------------------------------- /xorm.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "reflect" 8 | "runtime" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | Version string = "0.3.1" 14 | ) 15 | 16 | func close(engine *Engine) { 17 | engine.Close() 18 | } 19 | 20 | // new a db manager according to the parameter. Currently support four 21 | // drivers 22 | func NewEngine(driverName string, dataSourceName string) (*Engine, error) { 23 | engine := &Engine{DriverName: driverName, 24 | DataSourceName: dataSourceName, Filters: make([]Filter, 0)} 25 | engine.SetMapper(SnakeMapper{}) 26 | 27 | if driverName == SQLITE { 28 | engine.dialect = &sqlite3{} 29 | } else if driverName == MYSQL { 30 | engine.dialect = &mysql{} 31 | } else if driverName == POSTGRES { 32 | engine.dialect = &postgres{} 33 | engine.Filters = append(engine.Filters, &PgSeqFilter{}) 34 | engine.Filters = append(engine.Filters, &QuoteFilter{}) 35 | } else if driverName == MYMYSQL { 36 | engine.dialect = &mymysql{} 37 | } else if driverName == "odbc" { 38 | engine.dialect = &mssql{quoteFilter: &QuoteFilter{}} 39 | engine.Filters = append(engine.Filters, &QuoteFilter{}) 40 | } else if driverName == ORACLE_OCI { 41 | engine.dialect = &oracle{} 42 | engine.Filters = append(engine.Filters, &QuoteFilter{}) 43 | } else { 44 | return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) 45 | } 46 | err := engine.dialect.Init(driverName, dataSourceName) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | engine.Tables = make(map[reflect.Type]*Table) 52 | engine.mutex = &sync.RWMutex{} 53 | engine.TagIdentifier = "xorm" 54 | 55 | engine.Filters = append(engine.Filters, &IdFilter{}) 56 | engine.Logger = os.Stdout 57 | 58 | //engine.Pool = NewSimpleConnectPool() 59 | //engine.Pool = NewNoneConnectPool() 60 | //engine.Cacher = NewLRUCacher() 61 | err = engine.SetPool(NewSysConnectPool()) 62 | runtime.SetFinalizer(engine, close) 63 | return engine, err 64 | } 65 | -------------------------------------------------------------------------------- /xorm/c++.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | //"fmt" 5 | "github.com/lunny/xorm" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | var ( 11 | CPlusTmpl LangTmpl = LangTmpl{ 12 | template.FuncMap{"Mapper": mapper.Table2Obj, 13 | "Type": cPlusTypeStr, 14 | "UnTitle": unTitle, 15 | }, 16 | nil, 17 | genCPlusImports, 18 | } 19 | ) 20 | 21 | func cPlusTypeStr(col *xorm.Column) string { 22 | tp := col.SQLType 23 | name := strings.ToUpper(tp.Name) 24 | switch name { 25 | case xorm.Bit, xorm.TinyInt, xorm.SmallInt, xorm.MediumInt, xorm.Int, xorm.Integer, xorm.Serial: 26 | return "int" 27 | case xorm.BigInt, xorm.BigSerial: 28 | return "__int64" 29 | case xorm.Char, xorm.Varchar, xorm.TinyText, xorm.Text, xorm.MediumText, xorm.LongText: 30 | return "tstring" 31 | case xorm.Date, xorm.DateTime, xorm.Time, xorm.TimeStamp: 32 | return "time_t" 33 | case xorm.Decimal, xorm.Numeric: 34 | return "tstring" 35 | case xorm.Real, xorm.Float: 36 | return "float" 37 | case xorm.Double: 38 | return "double" 39 | case xorm.TinyBlob, xorm.Blob, xorm.MediumBlob, xorm.LongBlob, xorm.Bytea: 40 | return "tstring" 41 | case xorm.Bool: 42 | return "bool" 43 | default: 44 | return "tstring" 45 | } 46 | return "" 47 | } 48 | 49 | func genCPlusImports(tables []*xorm.Table) map[string]string { 50 | imports := make(map[string]string) 51 | 52 | for _, table := range tables { 53 | for _, col := range table.Columns { 54 | switch cPlusTypeStr(col) { 55 | case "time_t": 56 | imports[``] = `` 57 | case "tstring": 58 | imports[""] = "" 59 | //case "__int64": 60 | // imports[""] = "" 61 | } 62 | } 63 | } 64 | return imports 65 | } 66 | -------------------------------------------------------------------------------- /docs/ChangelogCN.md: -------------------------------------------------------------------------------- 1 | ## 更新日志 2 | 3 | * **v0.3.1** 4 | 5 | 新特性: 6 | * 支持 MSSQL DB 通过 ODBC 驱动 ([github.com/lunny/godbc](https://github.com/lunny/godbc)); 7 | * 通过多个pk标记支持联合主键; 8 | * 新增 Rows() API 用来遍历查询结果,该函数提供了类似sql.Rows的相似用法,可作为 Iterate() API 的可选替代; 9 | * ORM 结构体现在允许内建类型的指针作为成员,使得数据库为null成为可能; 10 | * Before 和 After 支持 11 | 12 | 改进: 13 | * 允许 int/int32/int64/uint/uint32/uint64/string 作为主键类型 14 | * 查询函数 Get()/Find()/Iterate() 在性能上的改进 15 | 16 | * **v0.2.3** : 改善了文档;提供了乐观锁支持;添加了带时区时间字段支持;Mapper现在分成表名Mapper和字段名Mapper,同时实现了表或字段的自定义前缀后缀;Insert方法的返回值含义从id, err更改为 affected, err,请大家注意;添加了UseBool 和 Distinct函数。 17 | * **v0.2.2** : Postgres驱动新增了对lib/pq的支持;新增了逐条遍历方法Iterate;新增了SetMaxConns(go1.2+)支持,修复了bug若干; 18 | * **v0.2.1** : 新增数据库反转工具,当前支持go和c++代码的生成,详见 [Xorm Tool README](https://github.com/lunny/xorm/blob/master/xorm/README.md); 修复了一些bug. 19 | * **v0.2.0** : 新增 [缓存](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#120)支持,查询速度提升3-5倍; 新增数据库表和Struct同名的映射方式; 新增Sync同步表结构; 20 | * **v0.1.9** : 新增 postgres 和 mymysql 驱动支持; 在Postgres中支持原始SQL语句中使用 ` 和 ? 符号; 新增Cols, StoreEngine, Charset 函数;SQL语句打印支持io.Writer接口,默认打印到控制台;新增更多的字段类型支持,详见 [映射规则](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#21);删除废弃的MakeSession和Create函数。 21 | * **v0.1.8** : 新增联合index,联合unique支持,请查看 [映射规则](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md#21)。 22 | * **v0.1.7** : 新增IConnectPool接口以及NoneConnectPool, SysConnectPool, SimpleConnectPool三种实现,可以选择不使用连接池,使用系统连接池和使用自带连接池三种实现,默认为SysConnectPool,即系统自带的连接池。同时支持自定义连接池。Engine新增Close方法,在系统退出时应调用此方法。 23 | * **v0.1.6** : 新增Conversion,支持自定义类型到数据库类型的转换;新增查询结构体自动检测匿名成员支持;新增单向映射支持; 24 | * **v0.1.5** : 新增对多线程的支持;新增Sql()函数;支持任意sql语句的struct查询;Get函数返回值变动;MakeSession和Create函数被NewSession和NewEngine函数替代; 25 | * **v0.1.4** : Get函数和Find函数新增简单的级联载入功能;对更多的数据库类型支持。 26 | * **v0.1.3** : Find函数现在支持传入Slice或者Map,当传入Map时,key为id;新增Table函数以为多表和临时表进行支持。 27 | * **v0.1.2** : Insert函数支持混合struct和slice指针传入,并根据数据库类型自动批量插入,同时自动添加事务 28 | * **v0.1.1** : 添加 Id, In 函数,改善 README 文档 29 | * **v0.1.0** : 初始化工程 30 | 31 | 32 | -------------------------------------------------------------------------------- /xorm/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | // A Command is an implementation of a go command 10 | // like go build or go fix. 11 | type Command struct { 12 | // Run runs the command. 13 | // The args are the arguments after the command name. 14 | Run func(cmd *Command, args []string) 15 | 16 | // UsageLine is the one-line usage message. 17 | // The first word in the line is taken to be the command name. 18 | UsageLine string 19 | 20 | // Short is the short description shown in the 'go help' output. 21 | Short string 22 | 23 | // Long is the long message shown in the 'go help ' output. 24 | Long string 25 | 26 | // Flag is a set of flags specific to this command. 27 | Flags map[string]bool 28 | } 29 | 30 | // Name returns the command's name: the first word in the usage line. 31 | func (c *Command) Name() string { 32 | name := c.UsageLine 33 | i := strings.Index(name, " ") 34 | if i >= 0 { 35 | name = name[:i] 36 | } 37 | return name 38 | } 39 | 40 | func (c *Command) Usage() { 41 | fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) 42 | fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) 43 | os.Exit(2) 44 | } 45 | 46 | // Runnable reports whether the command can be run; otherwise 47 | // it is a documentation pseudo-command such as importpath. 48 | func (c *Command) Runnable() bool { 49 | return c.Run != nil 50 | } 51 | 52 | // checkFlags checks if the flag exists with correct format. 53 | func checkFlags(flags map[string]bool, args []string, print func(string)) int { 54 | num := 0 // Number of valid flags, use to cut out. 55 | for i, f := range args { 56 | // Check flag prefix '-'. 57 | if !strings.HasPrefix(f, "-") { 58 | // Not a flag, finish check process. 59 | break 60 | } 61 | 62 | // Check if it a valid flag. 63 | if v, ok := flags[f]; ok { 64 | flags[f] = !v 65 | if !v { 66 | print(f) 67 | } else { 68 | fmt.Println("DISABLE: " + f) 69 | } 70 | } else { 71 | fmt.Printf("[ERRO] Unknown flag: %s.\n", f) 72 | return -1 73 | } 74 | num = i + 1 75 | } 76 | 77 | return num 78 | } 79 | -------------------------------------------------------------------------------- /examples/cache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lunny/xorm" 6 | _ "github.com/mattn/go-sqlite3" 7 | "os" 8 | ) 9 | 10 | type User struct { 11 | Id int64 12 | Name string 13 | } 14 | 15 | func main() { 16 | f := "cache.db" 17 | os.Remove(f) 18 | 19 | Orm, err := xorm.NewEngine("sqlite3", f) 20 | if err != nil { 21 | fmt.Println(err) 22 | return 23 | } 24 | Orm.ShowSQL = true 25 | cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) 26 | Orm.SetDefaultCacher(cacher) 27 | 28 | err = Orm.CreateTables(&User{}) 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | 34 | _, err = Orm.Insert(&User{Name: "xlw"}) 35 | if err != nil { 36 | fmt.Println(err) 37 | return 38 | } 39 | 40 | users := make([]User, 0) 41 | err = Orm.Find(&users) 42 | if err != nil { 43 | fmt.Println(err) 44 | return 45 | } 46 | 47 | fmt.Println("users:", users) 48 | 49 | users2 := make([]User, 0) 50 | 51 | err = Orm.Find(&users2) 52 | if err != nil { 53 | fmt.Println(err) 54 | return 55 | } 56 | 57 | fmt.Println("users2:", users2) 58 | 59 | users3 := make([]User, 0) 60 | 61 | err = Orm.Find(&users3) 62 | if err != nil { 63 | fmt.Println(err) 64 | return 65 | } 66 | 67 | fmt.Println("users3:", users3) 68 | 69 | user4 := new(User) 70 | has, err := Orm.Id(1).Get(user4) 71 | if err != nil { 72 | fmt.Println(err) 73 | return 74 | } 75 | 76 | fmt.Println("user4:", has, user4) 77 | 78 | user4.Name = "xiaolunwen" 79 | _, err = Orm.Id(1).Update(user4) 80 | if err != nil { 81 | fmt.Println(err) 82 | return 83 | } 84 | fmt.Println("user4:", user4) 85 | 86 | user5 := new(User) 87 | has, err = Orm.Id(1).Get(user5) 88 | if err != nil { 89 | fmt.Println(err) 90 | return 91 | } 92 | fmt.Println("user5:", has, user5) 93 | 94 | _, err = Orm.Id(1).Delete(new(User)) 95 | if err != nil { 96 | fmt.Println(err) 97 | return 98 | } 99 | 100 | for { 101 | user6 := new(User) 102 | has, err = Orm.Id(1).Get(user6) 103 | if err != nil { 104 | fmt.Println(err) 105 | return 106 | } 107 | fmt.Println("user6:", has, user6) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/sync.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/bylevel/pq" 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/lunny/xorm" 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | type SyncUser2 struct { 12 | Id int64 13 | Name string `xorm:"unique"` 14 | Age int `xorm:"index"` 15 | Title string 16 | Address string 17 | Genre string 18 | Area string 19 | Date int 20 | } 21 | 22 | type SyncLoginInfo2 struct { 23 | Id int64 24 | IP string `xorm:"index"` 25 | UserId int64 26 | AddedCol int 27 | // timestamp should be updated by database, so only allow get from db 28 | TimeStamp string 29 | // assume 30 | Nonuse int `xorm:"unique"` 31 | Newa string `xorm:"index"` 32 | } 33 | 34 | func sync(engine *xorm.Engine) error { 35 | return engine.Sync(&SyncLoginInfo2{}, &SyncUser2{}) 36 | } 37 | 38 | func sqliteEngine() (*xorm.Engine, error) { 39 | f := "sync.db" 40 | //os.Remove(f) 41 | 42 | return xorm.NewEngine("sqlite3", f) 43 | } 44 | 45 | func mysqlEngine() (*xorm.Engine, error) { 46 | return xorm.NewEngine("mysql", "root:@/test?charset=utf8") 47 | } 48 | 49 | func postgresEngine() (*xorm.Engine, error) { 50 | return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable") 51 | } 52 | 53 | type engineFunc func() (*xorm.Engine, error) 54 | 55 | func main() { 56 | //engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine} 57 | //engines := []engineFunc{sqliteEngine} 58 | //engines := []engineFunc{mysqlEngine} 59 | engines := []engineFunc{postgresEngine} 60 | for _, enginefunc := range engines { 61 | Orm, err := enginefunc() 62 | fmt.Println("--------", Orm.DriverName, "----------") 63 | if err != nil { 64 | fmt.Println(err) 65 | return 66 | } 67 | Orm.ShowSQL = true 68 | err = sync(Orm) 69 | if err != nil { 70 | fmt.Println(err) 71 | } 72 | 73 | _, err = Orm.Where("id > 0").Delete(&SyncUser2{}) 74 | if err != nil { 75 | fmt.Println(err) 76 | } 77 | 78 | user := &SyncUser2{ 79 | Name: "testsdf", 80 | Age: 15, 81 | Title: "newsfds", 82 | Address: "fasfdsafdsaf", 83 | Genre: "fsafd", 84 | Area: "fafdsafd", 85 | Date: 1000, 86 | } 87 | _, err = Orm.Insert(user) 88 | if err != nil { 89 | fmt.Println(err) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /docs/Changelog.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | * **v0.3.1** 4 | 5 | Features: 6 | * Support MSSQL DB via ODBC driver ([github.com/lunny/godbc](https://github.com/lunny/godbc)); 7 | * Composite Key, using multiple pk xorm tag 8 | * Added Row() API as alternative to Iterate() API for traversing result set, provide similar usages to sql.Rows type 9 | * ORM struct allowed declaration of pointer builtin type as members to allow null DB fields 10 | * Before and After Event processors 11 | 12 | Improvements: 13 | * Allowed int/int32/int64/uint/uint32/uint64/string as Primary Key type 14 | * Performance improvement for Get()/Find()/Iterate() 15 | 16 | 17 | * **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; 18 | * **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. 19 | * **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. 20 | * **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; 21 | * **v0.1.9** : Added postgres and mymysql supported; Added ` and ? supported on Raw SQL even if postgres; Added Cols, StoreEngine, Charset function, Added many column data type supported, please see [Mapping Rules](#mapping). 22 | * **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping). 23 | * **v0.1.7** : Added IConnectPool interface and NoneConnectPool, SysConnectPool, SimpleConnectPool the three implements. You can choose one of them and the default is SysConnectPool. You can customrize your own connection pool. struct Engine added Close method, It should be invoked before system exit. 24 | * **v0.1.6** : Added conversion interface support; added struct derive support; added single mapping support 25 | * **v0.1.5** : Added multi threads support; added Sql() function for struct query; Get function changed return inteface; MakeSession and Create are instead with NewSession and NewEngine. 26 | * **v0.1.4** : Added simple cascade load support; added more data type supports. 27 | * **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support 28 | * **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction 29 | * **v0.1.1** : Add Id, In functions and improved README 30 | * **v0.1.0** : Inital release. -------------------------------------------------------------------------------- /examples/maxconnect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/lunny/xorm" 7 | xorm "github.com/lunny/xorm" 8 | _ "github.com/mattn/go-sqlite3" 9 | "os" 10 | "runtime" 11 | ) 12 | 13 | type User struct { 14 | Id int64 15 | Name string 16 | } 17 | 18 | func sqliteEngine() (*xorm.Engine, error) { 19 | os.Remove("./test.db") 20 | return xorm.NewEngine("sqlite3", "./goroutine.db") 21 | } 22 | 23 | func mysqlEngine() (*xorm.Engine, error) { 24 | return xorm.NewEngine("mysql", "root:@/test?charset=utf8") 25 | } 26 | 27 | var u *User = &User{} 28 | 29 | func test(engine *xorm.Engine) { 30 | err := engine.CreateTables(u) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | 36 | engine.ShowSQL = true 37 | engine.Pool.SetMaxConns(5) 38 | size := 1000 39 | queue := make(chan int, size) 40 | 41 | for i := 0; i < size; i++ { 42 | go func(x int) { 43 | //x := i 44 | err := engine.Test() 45 | if err != nil { 46 | fmt.Println(err) 47 | } else { 48 | err = engine.Map(u) 49 | if err != nil { 50 | fmt.Println("Map user failed") 51 | } else { 52 | for j := 0; j < 10; j++ { 53 | if x+j < 2 { 54 | _, err = engine.Get(u) 55 | } else if x+j < 4 { 56 | users := make([]User, 0) 57 | err = engine.Find(&users) 58 | } else if x+j < 8 { 59 | _, err = engine.Count(u) 60 | } else if x+j < 16 { 61 | _, err = engine.Insert(&User{Name: "xlw"}) 62 | } else if x+j < 32 { 63 | _, err = engine.Id(1).Delete(u) 64 | } 65 | if err != nil { 66 | fmt.Println(err) 67 | queue <- x 68 | return 69 | } 70 | } 71 | fmt.Printf("%v success!\n", x) 72 | } 73 | } 74 | queue <- x 75 | }(i) 76 | } 77 | 78 | for i := 0; i < size; i++ { 79 | <-queue 80 | } 81 | 82 | fmt.Println("end") 83 | } 84 | 85 | func main() { 86 | runtime.GOMAXPROCS(2) 87 | fmt.Println("create engine") 88 | engine, err := sqliteEngine() 89 | if err != nil { 90 | fmt.Println(err) 91 | return 92 | } 93 | engine.ShowSQL = true 94 | fmt.Println(engine) 95 | test(engine) 96 | fmt.Println("------------------------") 97 | engine.Close() 98 | 99 | engine, err = mysqlEngine() 100 | if err != nil { 101 | fmt.Println(err) 102 | return 103 | } 104 | defer engine.Close() 105 | test(engine) 106 | } 107 | -------------------------------------------------------------------------------- /examples/cachegoroutine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/lunny/xorm" 7 | _ "github.com/mattn/go-sqlite3" 8 | "os" 9 | ) 10 | 11 | type User struct { 12 | Id int64 13 | Name string 14 | } 15 | 16 | func sqliteEngine() (*xorm.Engine, error) { 17 | os.Remove("./test.db") 18 | return xorm.NewEngine("sqlite3", "./goroutine.db") 19 | } 20 | 21 | func mysqlEngine() (*xorm.Engine, error) { 22 | return xorm.NewEngine("mysql", "root:@/test?charset=utf8") 23 | } 24 | 25 | var u *User = &User{} 26 | 27 | func test(engine *xorm.Engine) { 28 | err := engine.CreateTables(u) 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | 34 | size := 500 35 | queue := make(chan int, size) 36 | 37 | for i := 0; i < size; i++ { 38 | go func(x int) { 39 | //x := i 40 | err := engine.Ping() 41 | if err != nil { 42 | fmt.Println(err) 43 | } else { 44 | for j := 0; j < 10; j++ { 45 | if x+j < 2 { 46 | _, err = engine.Get(u) 47 | } else if x+j < 4 { 48 | users := make([]User, 0) 49 | err = engine.Find(&users) 50 | } else if x+j < 8 { 51 | _, err = engine.Count(u) 52 | } else if x+j < 16 { 53 | _, err = engine.Insert(&User{Name: "xlw"}) 54 | } else if x+j < 32 { 55 | //_, err = engine.Id(1).Delete(u) 56 | _, err = engine.Delete(u) 57 | } 58 | if err != nil { 59 | fmt.Println(err) 60 | queue <- x 61 | return 62 | } 63 | } 64 | fmt.Printf("%v success!\n", x) 65 | } 66 | queue <- x 67 | }(i) 68 | } 69 | 70 | for i := 0; i < size; i++ { 71 | <-queue 72 | } 73 | 74 | //conns := atomic.LoadInt32(&xorm.ConnectionNum) 75 | //fmt.Println("connection number:", conns) 76 | fmt.Println("end") 77 | } 78 | 79 | func main() { 80 | fmt.Println("-----start sqlite go routines-----") 81 | engine, err := sqliteEngine() 82 | if err != nil { 83 | fmt.Println(err) 84 | return 85 | } 86 | engine.ShowSQL = true 87 | cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) 88 | engine.SetDefaultCacher(cacher) 89 | fmt.Println(engine) 90 | test(engine) 91 | fmt.Println("test end") 92 | engine.Close() 93 | 94 | fmt.Println("-----start mysql go routines-----") 95 | engine, err = mysqlEngine() 96 | engine.ShowSQL = true 97 | cacher = xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) 98 | engine.SetDefaultCacher(cacher) 99 | if err != nil { 100 | fmt.Println(err) 101 | return 102 | } 103 | defer engine.Close() 104 | test(engine) 105 | } 106 | -------------------------------------------------------------------------------- /examples/goroutine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/lunny/xorm" 7 | _ "github.com/mattn/go-sqlite3" 8 | "os" 9 | "runtime" 10 | ) 11 | 12 | type User struct { 13 | Id int64 14 | Name string 15 | } 16 | 17 | func sqliteEngine() (*xorm.Engine, error) { 18 | os.Remove("./test.db") 19 | return xorm.NewEngine("sqlite3", "./goroutine.db") 20 | } 21 | 22 | func mysqlEngine() (*xorm.Engine, error) { 23 | return xorm.NewEngine("mysql", "root:@/test?charset=utf8") 24 | } 25 | 26 | var u *User = &User{} 27 | 28 | func test(engine *xorm.Engine) { 29 | err := engine.CreateTables(u) 30 | if err != nil { 31 | fmt.Println(err) 32 | return 33 | } 34 | 35 | size := 500 36 | queue := make(chan int, size) 37 | 38 | for i := 0; i < size; i++ { 39 | go func(x int) { 40 | //x := i 41 | err := engine.Test() 42 | if err != nil { 43 | fmt.Println(err) 44 | } else { 45 | err = engine.Map(u) 46 | if err != nil { 47 | fmt.Println("Map user failed") 48 | } else { 49 | for j := 0; j < 10; j++ { 50 | if x+j < 2 { 51 | _, err = engine.Get(u) 52 | } else if x+j < 4 { 53 | users := make([]User, 0) 54 | err = engine.Find(&users) 55 | } else if x+j < 8 { 56 | _, err = engine.Count(u) 57 | } else if x+j < 16 { 58 | _, err = engine.Insert(&User{Name: "xlw"}) 59 | } else if x+j < 32 { 60 | _, err = engine.Id(1).Delete(u) 61 | } 62 | if err != nil { 63 | fmt.Println(err) 64 | queue <- x 65 | return 66 | } 67 | } 68 | fmt.Printf("%v success!\n", x) 69 | } 70 | } 71 | queue <- x 72 | }(i) 73 | } 74 | 75 | for i := 0; i < size; i++ { 76 | <-queue 77 | } 78 | 79 | //conns := atomic.LoadInt32(&xorm.ConnectionNum) 80 | //fmt.Println("connection number:", conns) 81 | fmt.Println("end") 82 | } 83 | 84 | func main() { 85 | runtime.GOMAXPROCS(2) 86 | fmt.Println("-----start sqlite go routines-----") 87 | engine, err := sqliteEngine() 88 | if err != nil { 89 | fmt.Println(err) 90 | return 91 | } 92 | engine.ShowSQL = true 93 | fmt.Println(engine) 94 | test(engine) 95 | fmt.Println("test end") 96 | engine.Close() 97 | 98 | fmt.Println("-----start mysql go routines-----") 99 | engine, err = mysqlEngine() 100 | if err != nil { 101 | fmt.Println(err) 102 | return 103 | } 104 | defer engine.Close() 105 | test(engine) 106 | } 107 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # xorm 2 | 3 | [English](https://github.com/lunny/xorm/blob/master/README.md) 4 | 5 | xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 6 | 7 | [![Build Status](https://drone.io/github.com/lunny/xorm/status.png)](https://drone.io/github.com/lunny/xorm/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/lunny/xorm) 8 | 9 | ## 特性 10 | 11 | * 支持Struct和数据库表之间的灵活映射,并支持自动同步 12 | 13 | * 事务支持 14 | 15 | * 同时支持原始SQL语句和ORM操作的混合执行 16 | 17 | * 使用连写来简化调用 18 | 19 | * 支持使用Id, In, Where, Limit, Join, Having, Table, Sql, Cols等函数和结构体等方式作为条件 20 | 21 | * 支持级联加载Struct 22 | 23 | * 支持缓存 24 | 25 | * 支持根据数据库自动生成xorm的结构体 26 | 27 | * 支持记录版本(即乐观锁) 28 | 29 | ## 驱动支持 30 | 31 | 目前支持的Go数据库驱动和对应的数据库如下: 32 | 33 | * Mysql: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) 34 | 35 | * MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) 36 | 37 | * SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) 38 | 39 | * Postgres: [github.com/lib/pq](https://github.com/lib/pq) 40 | 41 | * MsSql: [github.com/lunny/godbc](https://github.com/lunny/godbc) 42 | 43 | ## 更新日志 44 | 45 | * **v0.3.1** 46 | 47 | 新特性: 48 | * 支持 MSSQL DB 通过 ODBC 驱动 ([github.com/lunny/godbc](https://github.com/lunny/godbc)); 49 | * 通过多个pk标记支持联合主键; 50 | * 新增 Rows() API 用来遍历查询结果,该函数提供了类似sql.Rows的相似用法,可作为 Iterate() API 的可选替代; 51 | * ORM 结构体现在允许内建类型的指针作为成员,使得数据库为null成为可能; 52 | * Before 和 After 支持 53 | 54 | 改进: 55 | * 允许 int/int32/int64/uint/uint32/uint64/string 作为主键类型 56 | * 查询函数 Get()/Find()/Iterate() 在性能上的改进 57 | 58 | 59 | [更多更新日志...](https://github.com/lunny/xorm/blob/master/docs/ChangelogCN.md) 60 | 61 | ## 安装 62 | 63 | 推荐使用 [gopm](https://github.com/gpmgo/gopm) 进行安装: 64 | 65 | gopm get github.com/lunny/xorm 66 | 67 | 或者您也可以使用go工具进行安装: 68 | 69 | go get github.com/lunny/xorm 70 | 71 | ## 文档 72 | 73 | * [快速开始](https://github.com/lunny/xorm/blob/master/docs/QuickStart.md) 74 | 75 | * [GoWalker代码文档](http://gowalker.org/github.com/lunny/xorm) 76 | 77 | * [Godoc代码文档](http://godoc.org/github.com/lunny/xorm) 78 | 79 | 80 | ## 案例 81 | 82 | * [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) 83 | 84 | * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) 85 | 86 | * [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) 87 | 88 | * [Sudo China](http://sudochina.com) - [github.com/insionng/toropress](http://github.com/insionng/toropress) 89 | 90 | * [Godaily](http://godaily.org) - [github.com/govc/godaily](http://github.com/govc/godaily) 91 | 92 | * [Very Hour](http://veryhour.com/) 93 | 94 | * [GoCMS - github.com/zzboy/GoCMS](https://github.com/zzdboy/GoCMS) 95 | 96 | ## 讨论 97 | 98 | 请加入QQ群:280360085 进行讨论。 99 | 100 | # 贡献者 101 | 102 | 如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/lunny/xorm/blob/master/CONTRIBUTING.md) 103 | 104 | * [Lunny](https://github.com/lunny) 105 | * [Nashtsai](https://github.com/nashtsai) 106 | 107 | ## LICENSE 108 | 109 | BSD License 110 | [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) 111 | -------------------------------------------------------------------------------- /sqlite3_test.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | func newSqlite3Engine() (*Engine, error) { 12 | os.Remove("./test.db") 13 | return NewEngine("sqlite3", "./test.db") 14 | } 15 | 16 | func newSqlite3DriverDB() (*sql.DB, error) { 17 | os.Remove("./test.db") 18 | return sql.Open("sqlite3", "./test.db") 19 | } 20 | 21 | func TestSqlite3(t *testing.T) { 22 | engine, err := newSqlite3Engine() 23 | defer engine.Close() 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | engine.ShowSQL = showTestSql 29 | engine.ShowErr = showTestSql 30 | engine.ShowWarn = showTestSql 31 | engine.ShowDebug = showTestSql 32 | 33 | testAll(engine, t) 34 | testAll2(engine, t) 35 | testAll3(engine, t) 36 | } 37 | 38 | func TestSqlite3WithCache(t *testing.T) { 39 | engine, err := newSqlite3Engine() 40 | defer engine.Close() 41 | if err != nil { 42 | t.Error(err) 43 | return 44 | } 45 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 46 | engine.ShowSQL = showTestSql 47 | engine.ShowErr = showTestSql 48 | engine.ShowWarn = showTestSql 49 | engine.ShowDebug = showTestSql 50 | 51 | testAll(engine, t) 52 | testAll2(engine, t) 53 | } 54 | 55 | const ( 56 | createTableSqlite3 = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);" 57 | dropTableSqlite3 = "DROP TABLE IF EXISTS `big_struct`;" 58 | ) 59 | 60 | func BenchmarkSqlite3DriverInsert(t *testing.B) { 61 | doBenchDriver(newSqlite3DriverDB, createTableSqlite3, dropTableSqlite3, 62 | doBenchDriverInsert, t) 63 | } 64 | 65 | func BenchmarkSqlite3DriverFind(t *testing.B) { 66 | doBenchDriver(newSqlite3DriverDB, createTableSqlite3, dropTableSqlite3, 67 | doBenchDriverFind, t) 68 | } 69 | 70 | func BenchmarkSqlite3NoCacheInsert(t *testing.B) { 71 | t.StopTimer() 72 | engine, err := newSqlite3Engine() 73 | defer engine.Close() 74 | if err != nil { 75 | t.Error(err) 76 | return 77 | } 78 | //engine.ShowSQL = true 79 | doBenchInsert(engine, t) 80 | } 81 | 82 | func BenchmarkSqlite3NoCacheFind(t *testing.B) { 83 | t.StopTimer() 84 | engine, err := newSqlite3Engine() 85 | defer engine.Close() 86 | if err != nil { 87 | t.Error(err) 88 | return 89 | } 90 | //engine.ShowSQL = true 91 | doBenchFind(engine, t) 92 | } 93 | 94 | func BenchmarkSqlite3NoCacheFindPtr(t *testing.B) { 95 | t.StopTimer() 96 | engine, err := newSqlite3Engine() 97 | defer engine.Close() 98 | if err != nil { 99 | t.Error(err) 100 | return 101 | } 102 | //engine.ShowSQL = true 103 | doBenchFindPtr(engine, t) 104 | } 105 | 106 | func BenchmarkSqlite3CacheInsert(t *testing.B) { 107 | t.StopTimer() 108 | engine, err := newSqlite3Engine() 109 | defer engine.Close() 110 | if err != nil { 111 | t.Error(err) 112 | return 113 | } 114 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 115 | doBenchInsert(engine, t) 116 | } 117 | 118 | func BenchmarkSqlite3CacheFind(t *testing.B) { 119 | t.StopTimer() 120 | engine, err := newSqlite3Engine() 121 | defer engine.Close() 122 | if err != nil { 123 | t.Error(err) 124 | return 125 | } 126 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 127 | doBenchFind(engine, t) 128 | } 129 | 130 | func BenchmarkSqlite3CacheFindPtr(t *testing.B) { 131 | t.StopTimer() 132 | engine, err := newSqlite3Engine() 133 | defer engine.Close() 134 | if err != nil { 135 | t.Error(err) 136 | return 137 | } 138 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 139 | doBenchFindPtr(engine, t) 140 | } 141 | -------------------------------------------------------------------------------- /mssql_test.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | // 4 | // +build windows 5 | 6 | import ( 7 | "database/sql" 8 | "testing" 9 | 10 | _ "github.com/lunny/godbc" 11 | ) 12 | 13 | const mssqlConnStr = "driver={SQL Server};Server=192.168.20.135;Database=xorm_test; uid=sa; pwd=1234;" 14 | 15 | func newMssqlEngine() (*Engine, error) { 16 | return NewEngine("odbc", mssqlConnStr) 17 | } 18 | 19 | func TestMssql(t *testing.T) { 20 | engine, err := newMssqlEngine() 21 | defer engine.Close() 22 | if err != nil { 23 | t.Error(err) 24 | return 25 | } 26 | engine.ShowSQL = showTestSql 27 | engine.ShowErr = showTestSql 28 | engine.ShowWarn = showTestSql 29 | engine.ShowDebug = showTestSql 30 | 31 | testAll(engine, t) 32 | testAll2(engine, t) 33 | } 34 | 35 | func TestMssqlWithCache(t *testing.T) { 36 | engine, err := newMssqlEngine() 37 | defer engine.Close() 38 | if err != nil { 39 | t.Error(err) 40 | return 41 | } 42 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 43 | engine.ShowSQL = showTestSql 44 | engine.ShowErr = showTestSql 45 | engine.ShowWarn = showTestSql 46 | engine.ShowDebug = showTestSql 47 | 48 | testAll(engine, t) 49 | testAll2(engine, t) 50 | } 51 | 52 | func newMssqlDriverDB() (*sql.DB, error) { 53 | return sql.Open("odbc", mssqlConnStr) 54 | } 55 | 56 | const ( 57 | createTableMssql = `IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = 'big_struct' ) CREATE TABLE 58 | "big_struct" ("id" BIGINT PRIMARY KEY IDENTITY NOT NULL, "name" VARCHAR(255) NULL, "title" VARCHAR(255) NULL, 59 | "age" VARCHAR(255) NULL, "alias" VARCHAR(255) NULL, "nick_name" VARCHAR(255) NULL); 60 | ` 61 | 62 | dropTableMssql = "IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'big_struct') and OBJECTPROPERTY(id, N'IsUserTable') = 1) DROP TABLE IF EXISTS `big_struct`;" 63 | ) 64 | 65 | func BenchmarkMssqlDriverInsert(t *testing.B) { 66 | doBenchDriver(newMssqlDriverDB, createTableMssql, dropTableMssql, 67 | doBenchDriverInsert, t) 68 | } 69 | 70 | func BenchmarkMssqlDriverFind(t *testing.B) { 71 | doBenchDriver(newMssqlDriverDB, createTableMssql, dropTableMssql, 72 | doBenchDriverFind, t) 73 | } 74 | 75 | func BenchmarkMssqlNoCacheInsert(t *testing.B) { 76 | engine, err := newMssqlEngine() 77 | defer engine.Close() 78 | if err != nil { 79 | t.Error(err) 80 | return 81 | } 82 | //engine.ShowSQL = true 83 | doBenchInsert(engine, t) 84 | } 85 | 86 | func BenchmarkMssqlNoCacheFind(t *testing.B) { 87 | engine, err := newMssqlEngine() 88 | defer engine.Close() 89 | if err != nil { 90 | t.Error(err) 91 | return 92 | } 93 | //engine.ShowSQL = true 94 | doBenchFind(engine, t) 95 | } 96 | 97 | func BenchmarkMssqlNoCacheFindPtr(t *testing.B) { 98 | engine, err := newMssqlEngine() 99 | defer engine.Close() 100 | if err != nil { 101 | t.Error(err) 102 | return 103 | } 104 | //engine.ShowSQL = true 105 | doBenchFindPtr(engine, t) 106 | } 107 | 108 | func BenchmarkMssqlCacheInsert(t *testing.B) { 109 | engine, err := newMssqlEngine() 110 | defer engine.Close() 111 | if err != nil { 112 | t.Error(err) 113 | return 114 | } 115 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 116 | 117 | doBenchInsert(engine, t) 118 | } 119 | 120 | func BenchmarkMssqlCacheFind(t *testing.B) { 121 | engine, err := newMssqlEngine() 122 | defer engine.Close() 123 | if err != nil { 124 | t.Error(err) 125 | return 126 | } 127 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 128 | 129 | doBenchFind(engine, t) 130 | } 131 | 132 | func BenchmarkMssqlCacheFindPtr(t *testing.B) { 133 | engine, err := newMssqlEngine() 134 | defer engine.Close() 135 | if err != nil { 136 | t.Error(err) 137 | return 138 | } 139 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 140 | 141 | doBenchFindPtr(engine, t) 142 | } 143 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func indexNoCase(s, sep string) int { 13 | return strings.Index(strings.ToLower(s), strings.ToLower(sep)) 14 | } 15 | 16 | func splitNoCase(s, sep string) []string { 17 | idx := indexNoCase(s, sep) 18 | if idx < 0 { 19 | return []string{s} 20 | } 21 | return strings.Split(s, s[idx:idx+len(sep)]) 22 | } 23 | 24 | func splitNNoCase(s, sep string, n int) []string { 25 | idx := indexNoCase(s, sep) 26 | if idx < 0 { 27 | return []string{s} 28 | } 29 | return strings.SplitN(s, s[idx:idx+len(sep)], n) 30 | } 31 | 32 | func makeArray(elem string, count int) []string { 33 | res := make([]string, count) 34 | for i := 0; i < count; i++ { 35 | res[i] = elem 36 | } 37 | return res 38 | } 39 | 40 | func rValue(bean interface{}) reflect.Value { 41 | return reflect.Indirect(reflect.ValueOf(bean)) 42 | } 43 | 44 | func rType(bean interface{}) reflect.Type { 45 | sliceValue := reflect.Indirect(reflect.ValueOf(bean)) 46 | //return reflect.TypeOf(sliceValue.Interface()) 47 | return sliceValue.Type() 48 | } 49 | 50 | func structName(v reflect.Type) string { 51 | for v.Kind() == reflect.Ptr { 52 | v = v.Elem() 53 | } 54 | return v.Name() 55 | } 56 | 57 | func sliceEq(left, right []string) bool { 58 | for _, l := range left { 59 | var find bool 60 | for _, r := range right { 61 | if l == r { 62 | find = true 63 | break 64 | } 65 | } 66 | if !find { 67 | return false 68 | } 69 | } 70 | 71 | return true 72 | } 73 | 74 | func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { 75 | 76 | aa := reflect.TypeOf((*rawValue).Interface()) 77 | vv := reflect.ValueOf((*rawValue).Interface()) 78 | 79 | var str string 80 | switch aa.Kind() { 81 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 82 | str = strconv.FormatInt(vv.Int(), 10) 83 | data = []byte(str) 84 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 85 | str = strconv.FormatUint(vv.Uint(), 10) 86 | data = []byte(str) 87 | case reflect.Float32, reflect.Float64: 88 | str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) 89 | data = []byte(str) 90 | case reflect.String: 91 | str = vv.String() 92 | data = []byte(str) 93 | case reflect.Array, reflect.Slice: 94 | switch aa.Elem().Kind() { 95 | case reflect.Uint8: 96 | data = rawValue.Interface().([]byte) 97 | default: 98 | err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) 99 | } 100 | //时间类型 101 | case reflect.Struct: 102 | if aa == reflect.TypeOf(c_TIME_DEFAULT) { 103 | str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano) 104 | data = []byte(str) 105 | } else { 106 | err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) 107 | } 108 | case reflect.Bool: 109 | str = strconv.FormatBool(vv.Bool()) 110 | data = []byte(str) 111 | case reflect.Complex128, reflect.Complex64: 112 | str = fmt.Sprintf("%v", vv.Complex()) 113 | data = []byte(str) 114 | /* TODO: unsupported types below 115 | case reflect.Map: 116 | case reflect.Ptr: 117 | case reflect.Uintptr: 118 | case reflect.UnsafePointer: 119 | case reflect.Chan, reflect.Func, reflect.Interface: 120 | */ 121 | default: 122 | err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) 123 | } 124 | return 125 | } 126 | 127 | func rows2maps(rows *sql.Rows) (resultsSlice []map[string][]byte, err error) { 128 | fields, err := rows.Columns() 129 | if err != nil { 130 | return nil, err 131 | } 132 | for rows.Next() { 133 | result, err := row2map(rows, fields) 134 | if err != nil { 135 | return nil, err 136 | } 137 | resultsSlice = append(resultsSlice, result) 138 | } 139 | 140 | return resultsSlice, nil 141 | } 142 | -------------------------------------------------------------------------------- /benchmark_base_test.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | ) 7 | 8 | type BigStruct struct { 9 | Id int64 10 | Name string 11 | Title string 12 | Age string 13 | Alias string 14 | NickName string 15 | } 16 | 17 | func doBenchDriverInsert(db *sql.DB, b *testing.B) { 18 | b.StartTimer() 19 | for i := 0; i < b.N; i++ { 20 | _, err := db.Exec(`insert into big_struct (name, title, age, alias, nick_name) 21 | values ('fafdasf', 'fadfa', 'afadfsaf', 'fadfafdsafd', 'fadfafdsaf')`) 22 | if err != nil { 23 | b.Error(err) 24 | return 25 | } 26 | } 27 | b.StopTimer() 28 | } 29 | 30 | func doBenchDriverFind(db *sql.DB, b *testing.B) { 31 | b.StopTimer() 32 | for i := 0; i < 50; i++ { 33 | _, err := db.Exec(`insert into big_struct (name, title, age, alias, nick_name) 34 | values ('fafdasf', 'fadfa', 'afadfsaf', 'fadfafdsafd', 'fadfafdsaf')`) 35 | if err != nil { 36 | b.Error(err) 37 | return 38 | } 39 | } 40 | 41 | b.StartTimer() 42 | for i := 0; i < b.N/50; i++ { 43 | rows, err := db.Query("select * from big_struct limit 50") 44 | if err != nil { 45 | b.Error(err) 46 | return 47 | } 48 | for rows.Next() { 49 | s := &BigStruct{} 50 | rows.Scan(&s.Id, &s.Name, &s.Title, &s.Age, &s.Alias, &s.NickName) 51 | } 52 | } 53 | b.StopTimer() 54 | } 55 | 56 | func doBenchDriver(newdriver func() (*sql.DB, error), createTableSql, 57 | dropTableSql string, opFunc func(*sql.DB, *testing.B), t *testing.B) { 58 | db, err := newdriver() 59 | if err != nil { 60 | t.Error(err) 61 | return 62 | } 63 | defer db.Close() 64 | 65 | _, err = db.Exec(createTableSql) 66 | if err != nil { 67 | t.Error(err) 68 | return 69 | } 70 | 71 | opFunc(db, t) 72 | 73 | _, err = db.Exec(dropTableSql) 74 | if err != nil { 75 | t.Error(err) 76 | return 77 | } 78 | } 79 | 80 | func doBenchInsert(engine *Engine, b *testing.B) { 81 | b.StopTimer() 82 | bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} 83 | err := engine.CreateTables(bs) 84 | if err != nil { 85 | b.Error(err) 86 | return 87 | } 88 | 89 | b.StartTimer() 90 | for i := 0; i < b.N; i++ { 91 | bs.Id = 0 92 | _, err = engine.Insert(bs) 93 | if err != nil { 94 | b.Error(err) 95 | return 96 | } 97 | } 98 | b.StopTimer() 99 | err = engine.DropTables(bs) 100 | if err != nil { 101 | b.Error(err) 102 | return 103 | } 104 | } 105 | 106 | func doBenchFind(engine *Engine, b *testing.B) { 107 | b.StopTimer() 108 | bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} 109 | err := engine.CreateTables(bs) 110 | if err != nil { 111 | b.Error(err) 112 | return 113 | } 114 | 115 | for i := 0; i < 100; i++ { 116 | bs.Id = 0 117 | _, err = engine.Insert(bs) 118 | if err != nil { 119 | b.Error(err) 120 | return 121 | } 122 | } 123 | 124 | b.StartTimer() 125 | for i := 0; i < b.N/50; i++ { 126 | bss := new([]BigStruct) 127 | err = engine.Limit(50).Find(bss) 128 | if err != nil { 129 | b.Error(err) 130 | return 131 | } 132 | } 133 | b.StopTimer() 134 | err = engine.DropTables(bs) 135 | if err != nil { 136 | b.Error(err) 137 | return 138 | } 139 | } 140 | 141 | func doBenchFindPtr(engine *Engine, b *testing.B) { 142 | b.StopTimer() 143 | bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} 144 | err := engine.CreateTables(bs) 145 | if err != nil { 146 | b.Error(err) 147 | return 148 | } 149 | 150 | for i := 0; i < 100; i++ { 151 | bs.Id = 0 152 | _, err = engine.Insert(bs) 153 | if err != nil { 154 | b.Error(err) 155 | return 156 | } 157 | } 158 | 159 | b.StartTimer() 160 | for i := 0; i < b.N/50; i++ { 161 | bss := new([]*BigStruct) 162 | err = engine.Limit(50).Find(bss) 163 | if err != nil { 164 | b.Error(err) 165 | return 166 | } 167 | } 168 | b.StopTimer() 169 | err = engine.DropTables(bs) 170 | if err != nil { 171 | b.Error(err) 172 | return 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /mymysql_test.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | _ "github.com/ziutek/mymysql/godrv" 8 | ) 9 | 10 | /* 11 | CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET 12 | utf8 COLLATE utf8_general_ci; 13 | */ 14 | 15 | var showTestSql bool = true 16 | 17 | func TestMyMysql(t *testing.T) { 18 | err := mymysqlDdlImport() 19 | if err != nil { 20 | t.Error(err) 21 | return 22 | } 23 | engine, err := NewEngine("mymysql", "xorm_test/root/") 24 | defer engine.Close() 25 | if err != nil { 26 | t.Error(err) 27 | return 28 | } 29 | engine.ShowSQL = showTestSql 30 | engine.ShowErr = showTestSql 31 | engine.ShowWarn = showTestSql 32 | engine.ShowDebug = showTestSql 33 | 34 | testAll(engine, t) 35 | testAll2(engine, t) 36 | testAll3(engine, t) 37 | } 38 | 39 | func TestMyMysqlWithCache(t *testing.T) { 40 | err := mymysqlDdlImport() 41 | if err != nil { 42 | t.Error(err) 43 | return 44 | } 45 | engine, err := NewEngine("mymysql", "xorm_test2/root/") 46 | defer engine.Close() 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 52 | engine.ShowSQL = showTestSql 53 | engine.ShowErr = showTestSql 54 | engine.ShowWarn = showTestSql 55 | engine.ShowDebug = showTestSql 56 | 57 | testAll(engine, t) 58 | testAll2(engine, t) 59 | } 60 | 61 | func newMyMysqlEngine() (*Engine, error) { 62 | return NewEngine("mymysql", "xorm_test2/root/") 63 | } 64 | 65 | func newMyMysqlDriverDB() (*sql.DB, error) { 66 | return sql.Open("mymysql", "xorm_test2/root/") 67 | } 68 | 69 | func BenchmarkMyMysqlDriverInsert(t *testing.B) { 70 | doBenchDriver(newMyMysqlDriverDB, createTableMySql, dropTableMySql, 71 | doBenchDriverInsert, t) 72 | } 73 | 74 | func BenchmarkMyMysqlDriverFind(t *testing.B) { 75 | doBenchDriver(newMyMysqlDriverDB, createTableMySql, dropTableMySql, 76 | doBenchDriverFind, t) 77 | } 78 | 79 | func mymysqlDdlImport() error { 80 | engine, err := NewEngine("mymysql", "/root/") 81 | if err != nil { 82 | return err 83 | } 84 | engine.ShowSQL = showTestSql 85 | engine.ShowErr = showTestSql 86 | engine.ShowWarn = showTestSql 87 | engine.ShowDebug = showTestSql 88 | 89 | sqlResults, _ := engine.Import("tests/mysql_ddl.sql") 90 | engine.LogDebug("sql results: %v", sqlResults) 91 | engine.Close() 92 | return nil 93 | } 94 | 95 | func BenchmarkMyMysqlNoCacheInsert(t *testing.B) { 96 | engine, err := newMyMysqlEngine() 97 | if err != nil { 98 | t.Error(err) 99 | return 100 | } 101 | defer engine.Close() 102 | 103 | doBenchInsert(engine, t) 104 | } 105 | 106 | func BenchmarkMyMysqlNoCacheFind(t *testing.B) { 107 | engine, err := newMyMysqlEngine() 108 | if err != nil { 109 | t.Error(err) 110 | return 111 | } 112 | defer engine.Close() 113 | 114 | //engine.ShowSQL = true 115 | doBenchFind(engine, t) 116 | } 117 | 118 | func BenchmarkMyMysqlNoCacheFindPtr(t *testing.B) { 119 | engine, err := newMyMysqlEngine() 120 | if err != nil { 121 | t.Error(err) 122 | return 123 | } 124 | defer engine.Close() 125 | 126 | //engine.ShowSQL = true 127 | doBenchFindPtr(engine, t) 128 | } 129 | 130 | func BenchmarkMyMysqlCacheInsert(t *testing.B) { 131 | engine, err := newMyMysqlEngine() 132 | if err != nil { 133 | t.Error(err) 134 | return 135 | } 136 | 137 | defer engine.Close() 138 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 139 | 140 | doBenchInsert(engine, t) 141 | } 142 | 143 | func BenchmarkMyMysqlCacheFind(t *testing.B) { 144 | engine, err := newMyMysqlEngine() 145 | if err != nil { 146 | t.Error(err) 147 | return 148 | } 149 | 150 | defer engine.Close() 151 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 152 | 153 | doBenchFind(engine, t) 154 | } 155 | 156 | func BenchmarkMyMysqlCacheFindPtr(t *testing.B) { 157 | engine, err := newMyMysqlEngine() 158 | if err != nil { 159 | t.Error(err) 160 | return 161 | } 162 | 163 | defer engine.Close() 164 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 165 | 166 | doBenchFindPtr(engine, t) 167 | } 168 | -------------------------------------------------------------------------------- /rows.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | type Rows struct { 10 | NoTypeCheck bool 11 | 12 | session *Session 13 | stmt *sql.Stmt 14 | rows *sql.Rows 15 | fields []string 16 | fieldsCount int 17 | beanType reflect.Type 18 | lastError error 19 | } 20 | 21 | func newRows(session *Session, bean interface{}) (*Rows, error) { 22 | rows := new(Rows) 23 | rows.session = session 24 | rows.beanType = reflect.Indirect(reflect.ValueOf(bean)).Type() 25 | 26 | err := rows.session.newDb() 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | defer rows.session.Statement.Init() 32 | 33 | var sql string 34 | var args []interface{} 35 | rows.session.Statement.RefTable = rows.session.Engine.autoMap(bean) 36 | if rows.session.Statement.RawSQL == "" { 37 | sql, args = rows.session.Statement.genGetSql(bean) 38 | } else { 39 | sql = rows.session.Statement.RawSQL 40 | args = rows.session.Statement.RawParams 41 | } 42 | 43 | for _, filter := range rows.session.Engine.Filters { 44 | sql = filter.Do(sql, session) 45 | } 46 | 47 | rows.session.Engine.LogSQL(sql) 48 | rows.session.Engine.LogSQL(args) 49 | 50 | rows.stmt, err = rows.session.Db.Prepare(sql) 51 | if err != nil { 52 | rows.lastError = err 53 | defer rows.Close() 54 | return nil, err 55 | } 56 | 57 | rows.rows, err = rows.stmt.Query(args...) 58 | if err != nil { 59 | rows.lastError = err 60 | defer rows.Close() 61 | return nil, err 62 | } 63 | 64 | rows.fields, err = rows.rows.Columns() 65 | if err != nil { 66 | rows.lastError = err 67 | defer rows.Close() 68 | return nil, err 69 | } 70 | rows.fieldsCount = len(rows.fields) 71 | 72 | return rows, nil 73 | } 74 | 75 | // move cursor to next record, return false if end has reached 76 | func (rows *Rows) Next() bool { 77 | if rows.lastError == nil && rows.rows != nil { 78 | hasNext := rows.rows.Next() 79 | if !hasNext { 80 | rows.lastError = sql.ErrNoRows 81 | } 82 | return hasNext 83 | } 84 | return false 85 | } 86 | 87 | // Err returns the error, if any, that was encountered during iteration. Err may be called after an explicit or implicit Close. 88 | func (rows *Rows) Err() error { 89 | return rows.lastError 90 | } 91 | 92 | // scan row record to bean properties 93 | func (rows *Rows) Scan(bean interface{}) error { 94 | if rows.lastError != nil { 95 | return rows.lastError 96 | } 97 | 98 | if !rows.NoTypeCheck && reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType { 99 | return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) 100 | } 101 | 102 | return rows.session.row2Bean(rows.rows, rows.fields, rows.fieldsCount, bean) 103 | 104 | // result, err := row2map(rows.rows, rows.fields) // !nashtsai! TODO remove row2map then scanMapIntoStruct conversation for better performance 105 | // if err == nil { 106 | // err = rows.session.scanMapIntoStruct(bean, result) 107 | // } 108 | // return err 109 | } 110 | 111 | // // Columns returns the column names. Columns returns an error if the rows are closed, or if the rows are from QueryRow and there was a deferred error. 112 | // func (rows *Rows) Columns() ([]string, error) { 113 | // if rows.lastError == nil && rows.rows != nil { 114 | // return rows.rows.Columns() 115 | // } 116 | // return nil, rows.lastError 117 | // } 118 | 119 | // close session if session.IsAutoClose is true, and claimed any opened resources 120 | func (rows *Rows) Close() error { 121 | if rows.session.IsAutoClose { 122 | defer rows.session.Close() 123 | } 124 | 125 | if rows.lastError == nil { 126 | if rows.rows != nil { 127 | rows.lastError = rows.rows.Close() 128 | if rows.lastError != nil { 129 | defer rows.stmt.Close() 130 | return rows.lastError 131 | } 132 | } 133 | if rows.stmt != nil { 134 | rows.lastError = rows.stmt.Close() 135 | } 136 | } else { 137 | if rows.stmt != nil { 138 | defer rows.stmt.Close() 139 | } 140 | if rows.rows != nil { 141 | defer rows.rows.Close() 142 | } 143 | } 144 | return rows.lastError 145 | } 146 | -------------------------------------------------------------------------------- /xorm/xorm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dvirsky/go-pylog/logging" 6 | "io" 7 | "os" 8 | "runtime" 9 | "strings" 10 | "sync" 11 | "text/template" 12 | "unicode" 13 | "unicode/utf8" 14 | ) 15 | 16 | // +build go1.1 17 | 18 | // Test that go1.1 tag above is included in builds. main.go refers to this definition. 19 | const go11tag = true 20 | 21 | const version = "0.1" 22 | 23 | // Commands lists the available commands and help topics. 24 | // The order here is the order in which they are printed by 'gopm help'. 25 | var commands = []*Command{ 26 | CmdReverse, 27 | CmdShell, 28 | } 29 | 30 | func init() { 31 | runtime.GOMAXPROCS(runtime.NumCPU()) 32 | } 33 | 34 | func main() { 35 | logging.SetLevel(logging.ALL) 36 | // Check length of arguments. 37 | args := os.Args[1:] 38 | if len(args) < 1 { 39 | usage() 40 | return 41 | } 42 | 43 | // Show help documentation. 44 | if args[0] == "help" { 45 | help(args[1:]) 46 | return 47 | } 48 | 49 | // Check commands and run. 50 | for _, comm := range commands { 51 | if comm.Name() == args[0] && comm.Run != nil { 52 | comm.Run(comm, args[1:]) 53 | exit() 54 | return 55 | } 56 | } 57 | 58 | fmt.Fprintf(os.Stderr, "xorm: unknown subcommand %q\nRun 'xorm help' for usage.\n", args[0]) 59 | setExitStatus(2) 60 | exit() 61 | } 62 | 63 | var exitStatus = 0 64 | var exitMu sync.Mutex 65 | 66 | func setExitStatus(n int) { 67 | exitMu.Lock() 68 | if exitStatus < n { 69 | exitStatus = n 70 | } 71 | exitMu.Unlock() 72 | } 73 | 74 | var usageTemplate = `xorm is a database tool based xorm package. 75 | Usage: 76 | 77 | xorm command [arguments] 78 | 79 | The commands are: 80 | {{range .}}{{if .Runnable}} 81 | {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 82 | 83 | Use "xorm help [command]" for more information about a command. 84 | 85 | Additional help topics: 86 | {{range .}}{{if not .Runnable}} 87 | {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 88 | 89 | Use "xorm help [topic]" for more information about that topic. 90 | 91 | ` 92 | 93 | var helpTemplate = `{{if .Runnable}}usage: xorm {{.UsageLine}} 94 | 95 | {{end}}{{.Long | trim}} 96 | ` 97 | 98 | // tmpl executes the given template text on data, writing the result to w. 99 | func tmpl(w io.Writer, text string, data interface{}) { 100 | t := template.New("top") 101 | t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) 102 | template.Must(t.Parse(text)) 103 | if err := t.Execute(w, data); err != nil { 104 | panic(err) 105 | } 106 | } 107 | 108 | func capitalize(s string) string { 109 | if s == "" { 110 | return s 111 | } 112 | r, n := utf8.DecodeRuneInString(s) 113 | return string(unicode.ToTitle(r)) + s[n:] 114 | } 115 | 116 | func printUsage(w io.Writer) { 117 | tmpl(w, usageTemplate, commands) 118 | } 119 | 120 | func usage() { 121 | printUsage(os.Stderr) 122 | os.Exit(2) 123 | } 124 | 125 | // help implements the 'help' command. 126 | func help(args []string) { 127 | if len(args) == 0 { 128 | printUsage(os.Stdout) 129 | // not exit 2: succeeded at 'gopm help'. 130 | return 131 | } 132 | if len(args) != 1 { 133 | fmt.Fprintf(os.Stderr, "usage: xorm help command\n\nToo many arguments given.\n") 134 | os.Exit(2) // failed at 'gopm help' 135 | } 136 | 137 | arg := args[0] 138 | 139 | for _, cmd := range commands { 140 | if cmd.Name() == arg { 141 | tmpl(os.Stdout, helpTemplate, cmd) 142 | // not exit 2: succeeded at 'gopm help cmd'. 143 | return 144 | } 145 | } 146 | 147 | fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'xorm help'.\n", arg) 148 | os.Exit(2) // failed at 'gopm help cmd' 149 | } 150 | 151 | var atexitFuncs []func() 152 | 153 | func atexit(f func()) { 154 | atexitFuncs = append(atexitFuncs, f) 155 | } 156 | 157 | func exit() { 158 | for _, f := range atexitFuncs { 159 | f() 160 | } 161 | os.Exit(exitStatus) 162 | } 163 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The XORM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package xorm is a simple and powerful ORM for Go. 7 | 8 | Installation 9 | 10 | Make sure you have installed Go 1.1+ and then: 11 | 12 | go get github.com/lunny/xorm 13 | 14 | Create Engine 15 | 16 | Firstly, we should new an engine for a database 17 | 18 | engine, err := xorm.NewEngine(driverName, dataSourceName) 19 | 20 | Method NewEngine's parameters is the same as sql.Open. It depends 21 | drivers' implementation. 22 | Generally, one engine is enough. You can set it as package variable. 23 | 24 | Raw Methods 25 | 26 | Xorm also support raw sql execution: 27 | 28 | 1. query sql, the returned results is []map[string][]byte 29 | 30 | results, err := engine.Query("select * from user") 31 | 32 | 2. exec sql, the returned results 33 | 34 | affected, err := engine.Exec("update user set .... where ...") 35 | 36 | ORM Methods 37 | 38 | There are 7 major ORM methods and many helpful methods to use to operate database. 39 | 40 | 1. Insert one or multipe records to database 41 | 42 | affected, err := engine.Insert(&struct) 43 | // INSERT INTO struct () values () 44 | affected, err := engine.Insert(&struct1, &struct2) 45 | // INSERT INTO struct1 () values () 46 | // INSERT INTO struct2 () values () 47 | affected, err := engine.Insert(&sliceOfStruct) 48 | // INSERT INTO struct () values (),(),() 49 | affected, err := engine.Insert(&struct1, &sliceOfStruct2) 50 | // INSERT INTO struct1 () values () 51 | // INSERT INTO struct2 () values (),(),() 52 | 53 | 2. Query one record from database 54 | 55 | has, err := engine.Get(&user) 56 | // SELECT * FROM user LIMIT 1 57 | 58 | 3. Query multiple records from database 59 | 60 | err := engine.Find(...) 61 | // SELECT * FROM user 62 | 63 | 4. Query multiple records and record by record handle, there two methods, one is Iterate, 64 | another is Raws 65 | 66 | raws, err := engine.Raws(...) 67 | // SELECT * FROM user 68 | for raws.Next() { 69 | raws.Scan(bean) 70 | } 71 | 72 | err := engine.Iterate(...) 73 | // SELECT * FROM user 74 | 75 | 5. Update one or more records 76 | 77 | affected, err := engine.Update(&user) 78 | // UPDATE user SET 79 | 80 | 6. Delete one or more records 81 | 82 | affected, err := engine.Delete(&user) 83 | // DELETE FROM user Where ... 84 | 85 | 7. Count records 86 | 87 | counts, err := engine.Count(&user) 88 | // SELECT count(*) AS total FROM user 89 | 90 | Conditions 91 | 92 | The above 7 methods could use with condition methods. 93 | 94 | 1. Id, In 95 | 96 | engine.Id(1).Get(&user) 97 | // SELECT * FROM user WHERE id = 1 98 | engine.In("id", 1, 2, 3).Find(&users) 99 | // SELECT * FROM user WHERE id IN (1, 2, 3) 100 | 101 | 2. Where, And, Or 102 | 103 | engine.Where().And().Or().Find() 104 | // SELECT * FROM user WHERE (.. AND ..) OR ... 105 | 106 | 3. OrderBy, Asc, Desc 107 | 108 | engine.Asc().Desc().Find() 109 | // SELECT * FROM user ORDER BY .. ASC, .. DESC 110 | engine.OrderBy().Find() 111 | // SELECT * FROM user ORDER BY .. 112 | 113 | 4. Limit, Top 114 | 115 | engine.Limit().Find() 116 | // SELECT * FROM user LIMIT .. OFFSET .. 117 | engine.Top().Find() 118 | // SELECT * FROM user LIMIT .. 119 | 120 | 5. Sql 121 | 122 | engine.Sql("select * from user").Find() 123 | 124 | 6. Cols, Omit, Distinct 125 | 126 | engine.Cols("col1, col2").Find() 127 | // SELECT col1, col2 FROM user 128 | engine.Omit("col1").Find() 129 | // SELECT col2, col3 FROM user 130 | engine.Distinct("col1").Find() 131 | // SELECT DISTINCT col1 FROM user 132 | 133 | 7. Join, GroupBy, Having 134 | 135 | engine.GroupBy("name").Having("name='xlw'").Find() 136 | //SELECT * FROM user GROUP BY name HAVING name='xlw' 137 | engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find() 138 | //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id 139 | 140 | More usage, please visit https://github.com/lunny/xorm/blob/master/docs/QuickStartEn.md 141 | */ 142 | package xorm 143 | -------------------------------------------------------------------------------- /mapper.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | ) 7 | 8 | // name translation between struct, fields names and table, column names 9 | type IMapper interface { 10 | Obj2Table(string) string 11 | Table2Obj(string) string 12 | } 13 | 14 | type CacheMapper struct { 15 | oriMapper IMapper 16 | obj2tableCache map[string]string 17 | obj2tableMutex sync.RWMutex 18 | table2objCache map[string]string 19 | table2objMutex sync.RWMutex 20 | } 21 | 22 | func NewCacheMapper(mapper IMapper) *CacheMapper { 23 | return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), 24 | table2objCache: make(map[string]string), 25 | } 26 | } 27 | 28 | func (m *CacheMapper) Obj2Table(o string) string { 29 | m.obj2tableMutex.RLock() 30 | t, ok := m.obj2tableCache[o] 31 | m.obj2tableMutex.RUnlock() 32 | if ok { 33 | return t 34 | } 35 | 36 | t = m.oriMapper.Obj2Table(o) 37 | m.obj2tableMutex.Lock() 38 | m.obj2tableCache[o] = t 39 | m.obj2tableMutex.Unlock() 40 | return t 41 | } 42 | 43 | func (m *CacheMapper) Table2Obj(t string) string { 44 | m.table2objMutex.RLock() 45 | o, ok := m.table2objCache[t] 46 | m.table2objMutex.RUnlock() 47 | if ok { 48 | return o 49 | } 50 | 51 | o = m.oriMapper.Table2Obj(t) 52 | m.table2objMutex.Lock() 53 | m.table2objCache[t] = o 54 | m.table2objMutex.Unlock() 55 | return o 56 | } 57 | 58 | // SameMapper implements IMapper and provides same name between struct and 59 | // database table 60 | type SameMapper struct { 61 | } 62 | 63 | func (m SameMapper) Obj2Table(o string) string { 64 | return o 65 | } 66 | 67 | func (m SameMapper) Table2Obj(t string) string { 68 | return t 69 | } 70 | 71 | // SnakeMapper implements IMapper and provides name transaltion between 72 | // struct and database table 73 | type SnakeMapper struct { 74 | } 75 | 76 | func snakeCasedName(name string) string { 77 | newstr := make([]rune, 0) 78 | for idx, chr := range name { 79 | if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { 80 | if idx > 0 { 81 | newstr = append(newstr, '_') 82 | } 83 | chr -= ('A' - 'a') 84 | } 85 | newstr = append(newstr, chr) 86 | } 87 | 88 | return string(newstr) 89 | } 90 | 91 | /*func pascal2Sql(s string) (d string) { 92 | d = "" 93 | lastIdx := 0 94 | for i := 0; i < len(s); i++ { 95 | if s[i] >= 'A' && s[i] <= 'Z' { 96 | if lastIdx < i { 97 | d += s[lastIdx+1 : i] 98 | } 99 | if i != 0 { 100 | d += "_" 101 | } 102 | d += string(s[i] + 32) 103 | lastIdx = i 104 | } 105 | } 106 | d += s[lastIdx+1:] 107 | return 108 | }*/ 109 | 110 | func (mapper SnakeMapper) Obj2Table(name string) string { 111 | return snakeCasedName(name) 112 | } 113 | 114 | func titleCasedName(name string) string { 115 | newstr := make([]rune, 0) 116 | upNextChar := true 117 | 118 | name = strings.ToLower(name) 119 | 120 | for _, chr := range name { 121 | switch { 122 | case upNextChar: 123 | upNextChar = false 124 | if 'a' <= chr && chr <= 'z' { 125 | chr -= ('a' - 'A') 126 | } 127 | case chr == '_': 128 | upNextChar = true 129 | continue 130 | } 131 | 132 | newstr = append(newstr, chr) 133 | } 134 | 135 | return string(newstr) 136 | } 137 | 138 | func (mapper SnakeMapper) Table2Obj(name string) string { 139 | return titleCasedName(name) 140 | } 141 | 142 | // provide prefix table name support 143 | type PrefixMapper struct { 144 | Mapper IMapper 145 | Prefix string 146 | } 147 | 148 | func (mapper PrefixMapper) Obj2Table(name string) string { 149 | return mapper.Prefix + mapper.Mapper.Obj2Table(name) 150 | } 151 | 152 | func (mapper PrefixMapper) Table2Obj(name string) string { 153 | return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) 154 | } 155 | 156 | func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { 157 | return PrefixMapper{mapper, prefix} 158 | } 159 | 160 | // provide suffix table name support 161 | type SuffixMapper struct { 162 | Mapper IMapper 163 | Suffix string 164 | } 165 | 166 | func (mapper SuffixMapper) Obj2Table(name string) string { 167 | return mapper.Suffix + mapper.Mapper.Obj2Table(name) 168 | } 169 | 170 | func (mapper SuffixMapper) Table2Obj(name string) string { 171 | return mapper.Mapper.Table2Obj(name[len(mapper.Suffix):]) 172 | } 173 | 174 | func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { 175 | return SuffixMapper{mapper, suffix} 176 | } 177 | -------------------------------------------------------------------------------- /mysql_test.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | /* 11 | CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET 12 | utf8 COLLATE utf8_general_ci; 13 | */ 14 | 15 | func TestMysql(t *testing.T) { 16 | err := mysqlDdlImport() 17 | if err != nil { 18 | t.Error(err) 19 | return 20 | } 21 | 22 | engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8") 23 | defer engine.Close() 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | engine.ShowSQL = showTestSql 29 | engine.ShowErr = showTestSql 30 | engine.ShowWarn = showTestSql 31 | engine.ShowDebug = showTestSql 32 | 33 | testAll(engine, t) 34 | testAll2(engine, t) 35 | testAll3(engine, t) 36 | } 37 | 38 | func TestMysqlSameMapper(t *testing.T) { 39 | err := mysqlDdlImport() 40 | if err != nil { 41 | t.Error(err) 42 | return 43 | } 44 | 45 | engine, err := NewEngine("mysql", "root:@/xorm_test3?charset=utf8") 46 | defer engine.Close() 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | engine.ShowSQL = showTestSql 52 | engine.ShowErr = showTestSql 53 | engine.ShowWarn = showTestSql 54 | engine.ShowDebug = showTestSql 55 | engine.SetMapper(SameMapper{}) 56 | 57 | testAll(engine, t) 58 | testAll2(engine, t) 59 | testAll3(engine, t) 60 | } 61 | 62 | func TestMysqlWithCache(t *testing.T) { 63 | err := mysqlDdlImport() 64 | if err != nil { 65 | t.Error(err) 66 | return 67 | } 68 | 69 | engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8") 70 | defer engine.Close() 71 | if err != nil { 72 | t.Error(err) 73 | return 74 | } 75 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 76 | engine.ShowSQL = showTestSql 77 | engine.ShowErr = showTestSql 78 | engine.ShowWarn = showTestSql 79 | engine.ShowDebug = showTestSql 80 | 81 | testAll(engine, t) 82 | testAll2(engine, t) 83 | } 84 | 85 | func newMysqlEngine() (*Engine, error) { 86 | return NewEngine("mysql", "root:@/xorm_test?charset=utf8") 87 | } 88 | 89 | func mysqlDdlImport() error { 90 | engine, err := NewEngine("mysql", "root:@/?charset=utf8") 91 | if err != nil { 92 | return err 93 | } 94 | engine.ShowSQL = showTestSql 95 | engine.ShowErr = showTestSql 96 | engine.ShowWarn = showTestSql 97 | engine.ShowDebug = showTestSql 98 | 99 | sqlResults, _ := engine.Import("tests/mysql_ddl.sql") 100 | engine.LogDebug("sql results: %v", sqlResults) 101 | engine.Close() 102 | return nil 103 | } 104 | 105 | func newMysqlDriverDB() (*sql.DB, error) { 106 | return sql.Open("mysql", "root:@/xorm_test?charset=utf8") 107 | } 108 | 109 | const ( 110 | createTableMySql = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NULL, `title` VARCHAR(255) NULL, `age` VARCHAR(255) NULL, `alias` VARCHAR(255) NULL, `nick_name` VARCHAR(255) NULL);" 111 | dropTableMySql = "DROP TABLE IF EXISTS `big_struct`;" 112 | ) 113 | 114 | func BenchmarkMysqlDriverInsert(t *testing.B) { 115 | doBenchDriver(newMysqlDriverDB, createTableMySql, dropTableMySql, 116 | doBenchDriverInsert, t) 117 | } 118 | 119 | func BenchmarkMysqlDriverFind(t *testing.B) { 120 | doBenchDriver(newMysqlDriverDB, createTableMySql, dropTableMySql, 121 | doBenchDriverFind, t) 122 | } 123 | 124 | func BenchmarkMysqlNoCacheInsert(t *testing.B) { 125 | engine, err := newMysqlEngine() 126 | defer engine.Close() 127 | if err != nil { 128 | t.Error(err) 129 | return 130 | } 131 | //engine.ShowSQL = true 132 | doBenchInsert(engine, t) 133 | } 134 | 135 | func BenchmarkMysqlNoCacheFind(t *testing.B) { 136 | engine, err := newMysqlEngine() 137 | defer engine.Close() 138 | if err != nil { 139 | t.Error(err) 140 | return 141 | } 142 | //engine.ShowSQL = true 143 | doBenchFind(engine, t) 144 | } 145 | 146 | func BenchmarkMysqlNoCacheFindPtr(t *testing.B) { 147 | engine, err := newMysqlEngine() 148 | defer engine.Close() 149 | if err != nil { 150 | t.Error(err) 151 | return 152 | } 153 | //engine.ShowSQL = true 154 | doBenchFindPtr(engine, t) 155 | } 156 | 157 | func BenchmarkMysqlCacheInsert(t *testing.B) { 158 | engine, err := newMysqlEngine() 159 | defer engine.Close() 160 | if err != nil { 161 | t.Error(err) 162 | return 163 | } 164 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 165 | 166 | doBenchInsert(engine, t) 167 | } 168 | 169 | func BenchmarkMysqlCacheFind(t *testing.B) { 170 | engine, err := newMysqlEngine() 171 | defer engine.Close() 172 | if err != nil { 173 | t.Error(err) 174 | return 175 | } 176 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 177 | 178 | doBenchFind(engine, t) 179 | } 180 | 181 | func BenchmarkMysqlCacheFindPtr(t *testing.B) { 182 | engine, err := newMysqlEngine() 183 | defer engine.Close() 184 | if err != nil { 185 | t.Error(err) 186 | return 187 | } 188 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 189 | 190 | doBenchFindPtr(engine, t) 191 | } 192 | -------------------------------------------------------------------------------- /xorm/shell.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lunny/xorm" 6 | "strings" 7 | ) 8 | 9 | var CmdShell = &Command{ 10 | UsageLine: "shell driverName datasourceName", 11 | Short: "a general shell to operate all kinds of database", 12 | Long: ` 13 | general database's shell for sqlite3, mysql, postgres. 14 | 15 | driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres 16 | datasourceName Database connection uri, for detail infomation please visit driver's project page 17 | `, 18 | } 19 | 20 | func init() { 21 | CmdShell.Run = runShell 22 | CmdShell.Flags = map[string]bool{} 23 | } 24 | 25 | var engine *xorm.Engine 26 | 27 | func shellHelp() { 28 | fmt.Println(` 29 | show tables show all tables 30 | columns show table's column info 31 | indexes show table's index info 32 | exit exit shell 33 | source exec sql file to current database 34 | dump [-nodata] dump structs or records to sql file 35 | help show this document 36 | SQL statement 37 | `) 38 | } 39 | 40 | func runShell(cmd *Command, args []string) { 41 | if len(args) != 2 { 42 | fmt.Println("params error, please see xorm help shell") 43 | return 44 | } 45 | 46 | var err error 47 | engine, err = xorm.NewEngine(args[0], args[1]) 48 | if err != nil { 49 | fmt.Println(err) 50 | return 51 | } 52 | 53 | err = engine.Ping() 54 | if err != nil { 55 | fmt.Println(err) 56 | return 57 | } 58 | 59 | var scmd string 60 | fmt.Print("xorm$ ") 61 | for { 62 | var input string 63 | _, err := fmt.Scan(&input) 64 | if err != nil { 65 | fmt.Println(err) 66 | continue 67 | } 68 | if strings.ToLower(input) == "exit" { 69 | fmt.Println("bye") 70 | return 71 | } 72 | if !strings.HasSuffix(input, ";") { 73 | scmd = scmd + " " + input 74 | continue 75 | } 76 | scmd = scmd + " " + input 77 | lcmd := strings.TrimSpace(strings.ToLower(scmd)) 78 | if strings.HasPrefix(lcmd, "select") { 79 | res, err := engine.Query(scmd + "\n") 80 | if err != nil { 81 | fmt.Println(err) 82 | } else { 83 | if len(res) <= 0 { 84 | fmt.Println("no records") 85 | } else { 86 | columns := make(map[string]int) 87 | for k, _ := range res[0] { 88 | columns[k] = len(k) 89 | } 90 | 91 | for _, m := range res { 92 | for k, s := range m { 93 | l := len(string(s)) 94 | if l > columns[k] { 95 | columns[k] = l 96 | } 97 | } 98 | } 99 | 100 | var maxlen = 0 101 | for _, l := range columns { 102 | maxlen = maxlen + l + 3 103 | } 104 | maxlen = maxlen + 1 105 | 106 | fmt.Println(strings.Repeat("-", maxlen)) 107 | fmt.Print("|") 108 | slice := make([]string, 0) 109 | for k, l := range columns { 110 | fmt.Print(" " + k + " ") 111 | fmt.Print(strings.Repeat(" ", l-len(k))) 112 | fmt.Print("|") 113 | slice = append(slice, k) 114 | } 115 | fmt.Print("\n") 116 | for _, r := range res { 117 | fmt.Print("|") 118 | for _, k := range slice { 119 | fmt.Print(" " + string(r[k]) + " ") 120 | fmt.Print(strings.Repeat(" ", columns[k]-len(string(r[k])))) 121 | fmt.Print("|") 122 | } 123 | fmt.Print("\n") 124 | } 125 | fmt.Println(strings.Repeat("-", maxlen)) 126 | //fmt.Println(res) 127 | } 128 | } 129 | } else if lcmd == "show tables;" { 130 | /*tables, err := engine.DBMetas() 131 | if err != nil { 132 | fmt.Println(err) 133 | } else { 134 | 135 | }*/ 136 | } else { 137 | cnt, err := engine.Exec(scmd) 138 | if err != nil { 139 | fmt.Println(err) 140 | } else { 141 | fmt.Printf("%d records changed.\n", cnt) 142 | } 143 | } 144 | scmd = "" 145 | fmt.Print("xorm$ ") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /sqlite3.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "strings" 6 | ) 7 | 8 | type sqlite3 struct { 9 | base 10 | } 11 | 12 | type sqlite3Parser struct { 13 | } 14 | 15 | func (p *sqlite3Parser) parse(driverName, dataSourceName string) (*uri, error) { 16 | return &uri{dbType: SQLITE, dbName: dataSourceName}, nil 17 | } 18 | 19 | func (db *sqlite3) Init(drivername, dataSourceName string) error { 20 | return db.base.init(&sqlite3Parser{}, drivername, dataSourceName) 21 | } 22 | 23 | func (db *sqlite3) SqlType(c *Column) string { 24 | switch t := c.SQLType.Name; t { 25 | case Date, DateTime, TimeStamp, Time: 26 | return Numeric 27 | case TimeStampz: 28 | return Text 29 | case Char, Varchar, TinyText, Text, MediumText, LongText: 30 | return Text 31 | case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: 32 | return Integer 33 | case Float, Double, Real: 34 | return Real 35 | case Decimal, Numeric: 36 | return Numeric 37 | case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: 38 | return Blob 39 | case Serial, BigSerial: 40 | c.IsPrimaryKey = true 41 | c.IsAutoIncrement = true 42 | c.Nullable = false 43 | return Integer 44 | default: 45 | return t 46 | } 47 | } 48 | 49 | func (db *sqlite3) SupportInsertMany() bool { 50 | return true 51 | } 52 | 53 | func (db *sqlite3) QuoteStr() string { 54 | return "`" 55 | } 56 | 57 | func (db *sqlite3) AutoIncrStr() string { 58 | return "AUTOINCREMENT" 59 | } 60 | 61 | func (db *sqlite3) SupportEngine() bool { 62 | return false 63 | } 64 | 65 | func (db *sqlite3) SupportCharset() bool { 66 | return false 67 | } 68 | 69 | func (db *sqlite3) IndexOnTable() bool { 70 | return false 71 | } 72 | 73 | func (db *sqlite3) IndexCheckSql(tableName, idxName string) (string, []interface{}) { 74 | args := []interface{}{idxName} 75 | return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args 76 | } 77 | 78 | func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) { 79 | args := []interface{}{tableName} 80 | return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args 81 | } 82 | 83 | func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interface{}) { 84 | args := []interface{}{tableName} 85 | sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" 86 | return sql, args 87 | } 88 | 89 | func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*Column, error) { 90 | args := []interface{}{tableName} 91 | s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" 92 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 93 | if err != nil { 94 | return nil, nil, err 95 | } 96 | defer cnn.Close() 97 | res, err := query(cnn, s, args...) 98 | if err != nil { 99 | return nil, nil, err 100 | } 101 | 102 | var sql string 103 | for _, record := range res { 104 | for name, content := range record { 105 | if name == "sql" { 106 | sql = string(content) 107 | } 108 | } 109 | } 110 | 111 | nStart := strings.Index(sql, "(") 112 | nEnd := strings.Index(sql, ")") 113 | colCreates := strings.Split(sql[nStart+1:nEnd], ",") 114 | cols := make(map[string]*Column) 115 | colSeq := make([]string, 0) 116 | for _, colStr := range colCreates { 117 | fields := strings.Fields(strings.TrimSpace(colStr)) 118 | col := new(Column) 119 | col.Indexes = make(map[string]bool) 120 | col.Nullable = true 121 | for idx, field := range fields { 122 | if idx == 0 { 123 | col.Name = strings.Trim(field, "`[] ") 124 | continue 125 | } else if idx == 1 { 126 | col.SQLType = SQLType{field, 0, 0} 127 | } 128 | switch field { 129 | case "PRIMARY": 130 | col.IsPrimaryKey = true 131 | case "AUTOINCREMENT": 132 | col.IsAutoIncrement = true 133 | case "NULL": 134 | if fields[idx-1] == "NOT" { 135 | col.Nullable = false 136 | } else { 137 | col.Nullable = true 138 | } 139 | } 140 | } 141 | cols[col.Name] = col 142 | colSeq = append(colSeq, col.Name) 143 | } 144 | return colSeq, cols, nil 145 | } 146 | 147 | func (db *sqlite3) GetTables() ([]*Table, error) { 148 | args := []interface{}{} 149 | s := "SELECT name FROM sqlite_master WHERE type='table'" 150 | 151 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 152 | if err != nil { 153 | return nil, err 154 | } 155 | defer cnn.Close() 156 | res, err := query(cnn, s, args...) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | tables := make([]*Table, 0) 162 | for _, record := range res { 163 | table := new(Table) 164 | for name, content := range record { 165 | switch name { 166 | case "name": 167 | table.Name = string(content) 168 | } 169 | } 170 | if table.Name == "sqlite_sequence" { 171 | continue 172 | } 173 | tables = append(tables, table) 174 | } 175 | return tables, nil 176 | } 177 | 178 | func (db *sqlite3) GetIndexes(tableName string) (map[string]*Index, error) { 179 | args := []interface{}{tableName} 180 | s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" 181 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 182 | if err != nil { 183 | return nil, err 184 | } 185 | defer cnn.Close() 186 | res, err := query(cnn, s, args...) 187 | if err != nil { 188 | return nil, err 189 | } 190 | 191 | indexes := make(map[string]*Index, 0) 192 | for _, record := range res { 193 | index := new(Index) 194 | sql := string(record["sql"]) 195 | 196 | if sql == "" { 197 | continue 198 | } 199 | 200 | nNStart := strings.Index(sql, "INDEX") 201 | nNEnd := strings.Index(sql, "ON") 202 | if nNStart == -1 || nNEnd == -1 { 203 | continue 204 | } 205 | 206 | indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") 207 | //fmt.Println(indexName) 208 | if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { 209 | index.Name = indexName[5+len(tableName) : len(indexName)] 210 | } else { 211 | index.Name = indexName 212 | } 213 | 214 | if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { 215 | index.Type = UniqueType 216 | } else { 217 | index.Type = IndexType 218 | } 219 | 220 | nStart := strings.Index(sql, "(") 221 | nEnd := strings.Index(sql, ")") 222 | colIndexes := strings.Split(sql[nStart+1:nEnd], ",") 223 | 224 | index.Cols = make([]string, 0) 225 | for _, col := range colIndexes { 226 | index.Cols = append(index.Cols, strings.Trim(col, "` []")) 227 | } 228 | indexes[index.Name] = index 229 | } 230 | 231 | return indexes, nil 232 | } 233 | -------------------------------------------------------------------------------- /docs/COLUMNTYPE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 17 | 19 | 21 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 44 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 57 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 70 | 72 | 74 | 75 | 76 | 77 | 78 | 80 | 82 | 84 | 86 | 87 | 88 | 89 | 90 | 91 | 93 | 95 | 97 | 99 | 100 | 101 | 102 | 103 | 104 | 106 | 108 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 119 | 121 | 123 | 125 | 126 | 127 | 128 | 129 | 130 | 132 | 134 | 136 | 138 | 139 | 140 | 141 | 142 | 144 | 146 | 148 | 150 | 151 | 152 | 153 | 154 | 156 | 158 | 160 | 162 | 163 | 164 | 165 | 166 | 167 | 169 | 171 | 173 | 175 | 176 | 177 | 178 | 179 | 180 | 182 | 184 | 186 | 188 | 189 | 190 | 191 | 192 | 193 | 195 | 197 | 199 | 201 | 202 | 203 | 204 | 205 | 206 | 208 | 210 | 212 | 214 | 215 | 216 | 217 | 218 | 219 | 221 | 223 | 225 | 227 | 228 | 229 | 230 | 231 | 232 | 234 | 236 | 238 | 240 | 241 | 242 | 243 | 244 | 245 | 247 | 249 | 251 | 253 | 254 | 255 | 256 | 257 | 258 | 260 | 262 | 264 | 266 | 267 | 268 | 269 | 270 | 271 | 273 | 275 | 277 | 279 | 280 | 281 | 282 | 283 | 284 | 286 | 288 | 290 | 292 | 293 | 294 | 295 | 296 | 297 | 299 | 301 | 303 | 305 | 306 | 307 | 308 | 309 | 310 | 312 | 314 | 316 | 318 | 319 | 320 | 321 | 322 | 323 | 325 | 327 | 329 | 331 | 332 | 333 | 334 | 335 | 336 | 338 | 340 | 342 | 344 | 345 | 346 | 347 | 348 | 349 | 351 | 353 | 355 | 357 | 358 | 359 | 360 | 361 | 362 | 364 | 366 | 368 | 370 | 371 | 372 | 373 | 374 | 375 | 377 | 379 | 381 | 383 | 384 | 385 | 386 | 387 | 389 | 391 | 393 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 403 | 405 | 407 | 409 | 410 | 411 | 412 | 413 | 414 | 416 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 430 | 432 | 434 | 435 | 436 | 437 | 438 |
xorm 4 | mysql 6 | sqlite3 8 | postgres 10 | remark
BIT 16 | BIT 18 | INTEGER 20 | BIT 22 |
TINYINT 28 | TINYINT 30 | INTEGER 32 | SMALLINT 34 |
SMALLINT 41 | SMALLINT 43 | INTEGER 45 | SMALLINT 47 |
MEDIUMINT 54 | MEDIUMINT 56 | INTEGER 58 | INTEGER 60 |
INT 67 | INT 69 | INTEGER 71 | INTEGER 73 |
INTEGER 79 | INTEGER 81 | INTEGER 83 | INTEGER 85 |
BIGINT 92 | BIGINT 94 | INTEGER 96 | BIGINT 98 |
CHAR 105 | CHAR 107 | TEXT 109 | CHAR 111 |
VARCHAR 118 | VARCHAR 120 | TEXT 122 | VARCHAR 124 |
TINYTEXT 131 | TINYTEXT 133 | TEXT 135 | TEXT 137 |
TEXT 143 | TEXT 145 | TEXT 147 | TEXT 149 |
MEDIUMTEXT 155 | MEDIUMTEXT 157 | TEXT 159 | TEXT 161 |
LONGTEXT 168 | LONGTEXT 170 | TEXT 172 | TEXT 174 |
BINARY 181 | BINARY 183 | BLOB 185 | BYTEA 187 |
VARBINARY 194 | VARBINARY 196 | BLOB 198 | BYTEA 200 |
DATE 207 | DATE 209 | NUMERIC 211 | DATE 213 |
DATETIME 220 | DATETIME 222 | NUMERIC 224 | TIMESTAMP 226 |
TIME 233 | TIME 235 | NUMERIC 237 | TIME 239 |
TIMESTAMP 246 | TIMESTAMP 248 | NUMERIC 250 | TIMESTAMP 252 |
TIMESTAMPZ 259 | TEXT 261 | TEXT 263 | TIMESTAMP with zone 265 | timestamp with zone info
REAL 272 | REAL 274 | REAL 276 | REAL 278 |
FLOAT 285 | FLOAT 287 | REAL 289 | REAL 291 |
DOUBLE 298 | DOUBLE 300 | REAL 302 | DOUBLE PRECISION 304 |
DECIMAL 311 | DECIMAL 313 | NUMERIC 315 | DECIMAL 317 |
NUMERIC 324 | NUMERIC 326 | NUMERIC 328 | NUMERIC 330 |
TINYBLOB 337 | TINYBLOB 339 | BLOB 341 | BYTEA 343 |
BLOB 350 | BLOB 352 | BLOB 354 | BYTEA 356 |
MEDIUMBLOB 363 | MEDIUMBLOB 365 | BLOB 367 | BYTEA 369 |
LONGBLOB 376 | LONGBLOB 378 | BLOB 380 | BYTEA 382 |
BYTEA 388 | BLOB 390 | BLOB 392 | BYTEA 394 |
BOOL 402 | TINYINT 404 | INTEGER 406 | BOOLEAN 408 |
SERIAL 415 | INT 417 | INTEGER 419 | SERIAL 421 | auto increment
BIGSERIAL 427 | BIGINT 429 | INTEGER 431 | BIGSERIAL 433 | auto increment
-------------------------------------------------------------------------------- /xorm/reverse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "strconv" 11 | "strings" //[SWH|+] 12 | "text/template" 13 | 14 | "github.com/dvirsky/go-pylog/logging" 15 | _ "github.com/go-sql-driver/mysql" 16 | _ "github.com/lib/pq" 17 | "github.com/lunny/xorm" 18 | _ "github.com/mattn/go-sqlite3" 19 | _ "github.com/ziutek/mymysql/godrv" 20 | ) 21 | 22 | var CmdReverse = &Command{ 23 | UsageLine: "reverse [-m] driverName datasourceName tmplPath [generatedPath]", 24 | Short: "reverse a db to codes", 25 | Long: ` 26 | according database's tables and columns to generate codes for Go, C++ and etc. 27 | 28 | -m Generated one go file for every table 29 | driverName Database driver name, now supported four: mysql mymysql sqlite3 postgres 30 | datasourceName Database connection uri, for detail infomation please visit driver's project page 31 | tmplPath Template dir for generated. the default templates dir has provide 1 template 32 | generatedPath This parameter is optional, if blank, the default value is model, then will 33 | generated all codes in model dir 34 | `, 35 | } 36 | 37 | func init() { 38 | CmdReverse.Run = runReverse 39 | CmdReverse.Flags = map[string]bool{ 40 | "-s": false, 41 | "-l": false, 42 | } 43 | } 44 | 45 | var ( 46 | genJson bool = false 47 | ) 48 | 49 | func printReversePrompt(flag string) { 50 | } 51 | 52 | type Tmpl struct { 53 | Tables []*xorm.Table 54 | Imports map[string]string 55 | Model string 56 | } 57 | 58 | func dirExists(dir string) bool { 59 | d, e := os.Stat(dir) 60 | switch { 61 | case e != nil: 62 | return false 63 | case !d.IsDir(): 64 | return false 65 | } 66 | 67 | return true 68 | } 69 | 70 | func runReverse(cmd *Command, args []string) { 71 | num := checkFlags(cmd.Flags, args, printReversePrompt) 72 | if num == -1 { 73 | return 74 | } 75 | args = args[num:] 76 | 77 | if len(args) < 3 { 78 | fmt.Println("params error, please see xorm help reverse") 79 | return 80 | } 81 | 82 | var isMultiFile bool = true 83 | if use, ok := cmd.Flags["-s"]; ok { 84 | isMultiFile = !use 85 | } 86 | 87 | curPath, err := os.Getwd() 88 | if err != nil { 89 | fmt.Println(curPath) 90 | return 91 | } 92 | 93 | var genDir string 94 | var model string 95 | if len(args) == 4 { 96 | 97 | genDir, err = filepath.Abs(args[3]) 98 | if err != nil { 99 | fmt.Println(err) 100 | return 101 | } 102 | //[SWH|+] 经测试,path.Base不能解析windows下的“\”,需要替换为“/” 103 | genDir = strings.Replace(genDir, "\\", "/", -1) 104 | model = path.Base(genDir) 105 | } else { 106 | model = "model" 107 | genDir = path.Join(curPath, model) 108 | } 109 | 110 | dir, err := filepath.Abs(args[2]) 111 | if err != nil { 112 | logging.Error("%v", err) 113 | return 114 | } 115 | 116 | if !dirExists(dir) { 117 | logging.Error("Template %v path is not exist", dir) 118 | return 119 | } 120 | 121 | var langTmpl LangTmpl 122 | var ok bool 123 | var lang string = "go" 124 | var prefix string = "" //[SWH|+] 125 | cfgPath := path.Join(dir, "config") 126 | info, err := os.Stat(cfgPath) 127 | var configs map[string]string 128 | if err == nil && !info.IsDir() { 129 | configs = loadConfig(cfgPath) 130 | if l, ok := configs["lang"]; ok { 131 | lang = l 132 | } 133 | if j, ok := configs["genJson"]; ok { 134 | genJson, err = strconv.ParseBool(j) 135 | } 136 | //[SWH|+] 137 | if j, ok := configs["prefix"]; ok { 138 | prefix = j 139 | } 140 | } 141 | 142 | if langTmpl, ok = langTmpls[lang]; !ok { 143 | fmt.Println("Unsupported programing language", lang) 144 | return 145 | } 146 | 147 | os.MkdirAll(genDir, os.ModePerm) 148 | 149 | Orm, err := xorm.NewEngine(args[0], args[1]) 150 | if err != nil { 151 | logging.Error("%v", err) 152 | return 153 | } 154 | 155 | tables, err := Orm.DBMetas() 156 | if err != nil { 157 | logging.Error("%v", err) 158 | return 159 | } 160 | 161 | filepath.Walk(dir, func(f string, info os.FileInfo, err error) error { 162 | if info.IsDir() { 163 | return nil 164 | } 165 | 166 | if info.Name() == "config" { 167 | return nil 168 | } 169 | 170 | bs, err := ioutil.ReadFile(f) 171 | if err != nil { 172 | logging.Error("%v", err) 173 | return err 174 | } 175 | 176 | t := template.New(f) 177 | t.Funcs(langTmpl.Funcs) 178 | 179 | tmpl, err := t.Parse(string(bs)) 180 | if err != nil { 181 | logging.Error("%v", err) 182 | return err 183 | } 184 | 185 | var w *os.File 186 | fileName := info.Name() 187 | newFileName := fileName[:len(fileName)-4] 188 | ext := path.Ext(newFileName) 189 | 190 | if !isMultiFile { 191 | w, err = os.OpenFile(path.Join(genDir, newFileName), os.O_RDWR|os.O_CREATE, 0600) 192 | if err != nil { 193 | logging.Error("%v", err) 194 | return err 195 | } 196 | 197 | imports := langTmpl.GenImports(tables) 198 | tbls := make([]*xorm.Table, 0) 199 | for _, table := range tables { 200 | //[SWH|+] 201 | if prefix != "" { 202 | table.Name = strings.TrimPrefix(table.Name, prefix) 203 | } 204 | tbls = append(tbls, table) 205 | } 206 | 207 | newbytes := bytes.NewBufferString("") 208 | 209 | t := &Tmpl{Tables: tbls, Imports: imports, Model: model} 210 | err = tmpl.Execute(newbytes, t) 211 | if err != nil { 212 | logging.Error("%v", err) 213 | return err 214 | } 215 | 216 | tplcontent, err := ioutil.ReadAll(newbytes) 217 | if err != nil { 218 | logging.Error("%v", err) 219 | return err 220 | } 221 | var source string 222 | if langTmpl.Formater != nil { 223 | source, err = langTmpl.Formater(string(tplcontent)) 224 | if err != nil { 225 | logging.Error("%v", err) 226 | return err 227 | } 228 | } else { 229 | source = string(tplcontent) 230 | } 231 | 232 | w.WriteString(source) 233 | w.Close() 234 | } else { 235 | for _, table := range tables { 236 | //[SWH|+] 237 | if prefix != "" { 238 | table.Name = strings.TrimPrefix(table.Name, prefix) 239 | } 240 | // imports 241 | tbs := []*xorm.Table{table} 242 | imports := langTmpl.GenImports(tbs) 243 | w, err := os.OpenFile(path.Join(genDir, unTitle(mapper.Table2Obj(table.Name))+ext), os.O_RDWR|os.O_CREATE, 0600) 244 | if err != nil { 245 | logging.Error("%v", err) 246 | return err 247 | } 248 | 249 | newbytes := bytes.NewBufferString("") 250 | 251 | t := &Tmpl{Tables: tbs, Imports: imports, Model: model} 252 | err = tmpl.Execute(newbytes, t) 253 | if err != nil { 254 | logging.Error("%v", err) 255 | return err 256 | } 257 | 258 | tplcontent, err := ioutil.ReadAll(newbytes) 259 | if err != nil { 260 | logging.Error("%v", err) 261 | return err 262 | } 263 | var source string 264 | if langTmpl.Formater != nil { 265 | source, err = langTmpl.Formater(string(tplcontent)) 266 | if err != nil { 267 | logging.Error("%v-%v", err, string(tplcontent)) 268 | return err 269 | } 270 | } else { 271 | source = string(tplcontent) 272 | } 273 | 274 | w.WriteString(source) 275 | w.Close() 276 | } 277 | } 278 | 279 | return nil 280 | }) 281 | 282 | } 283 | -------------------------------------------------------------------------------- /xorm/go.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/lunny/xorm" 7 | "go/format" 8 | "reflect" 9 | "strings" 10 | "text/template" 11 | ) 12 | 13 | var ( 14 | GoLangTmpl LangTmpl = LangTmpl{ 15 | template.FuncMap{"Mapper": mapper.Table2Obj, 16 | "Type": typestring, 17 | "Tag": tag, 18 | "UnTitle": unTitle, 19 | "gt": gt, 20 | "getCol": getCol, 21 | }, 22 | formatGo, 23 | genGoImports, 24 | } 25 | ) 26 | 27 | var ( 28 | errBadComparisonType = errors.New("invalid type for comparison") 29 | errBadComparison = errors.New("incompatible types for comparison") 30 | errNoComparison = errors.New("missing argument for comparison") 31 | ) 32 | 33 | type kind int 34 | 35 | const ( 36 | invalidKind kind = iota 37 | boolKind 38 | complexKind 39 | intKind 40 | floatKind 41 | integerKind 42 | stringKind 43 | uintKind 44 | ) 45 | 46 | func basicKind(v reflect.Value) (kind, error) { 47 | switch v.Kind() { 48 | case reflect.Bool: 49 | return boolKind, nil 50 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 51 | return intKind, nil 52 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 53 | return uintKind, nil 54 | case reflect.Float32, reflect.Float64: 55 | return floatKind, nil 56 | case reflect.Complex64, reflect.Complex128: 57 | return complexKind, nil 58 | case reflect.String: 59 | return stringKind, nil 60 | } 61 | return invalidKind, errBadComparisonType 62 | } 63 | 64 | // eq evaluates the comparison a == b || a == c || ... 65 | func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { 66 | v1 := reflect.ValueOf(arg1) 67 | k1, err := basicKind(v1) 68 | if err != nil { 69 | return false, err 70 | } 71 | if len(arg2) == 0 { 72 | return false, errNoComparison 73 | } 74 | for _, arg := range arg2 { 75 | v2 := reflect.ValueOf(arg) 76 | k2, err := basicKind(v2) 77 | if err != nil { 78 | return false, err 79 | } 80 | if k1 != k2 { 81 | return false, errBadComparison 82 | } 83 | truth := false 84 | switch k1 { 85 | case boolKind: 86 | truth = v1.Bool() == v2.Bool() 87 | case complexKind: 88 | truth = v1.Complex() == v2.Complex() 89 | case floatKind: 90 | truth = v1.Float() == v2.Float() 91 | case intKind: 92 | truth = v1.Int() == v2.Int() 93 | case stringKind: 94 | truth = v1.String() == v2.String() 95 | case uintKind: 96 | truth = v1.Uint() == v2.Uint() 97 | default: 98 | panic("invalid kind") 99 | } 100 | if truth { 101 | return true, nil 102 | } 103 | } 104 | return false, nil 105 | } 106 | 107 | // lt evaluates the comparison a < b. 108 | func lt(arg1, arg2 interface{}) (bool, error) { 109 | v1 := reflect.ValueOf(arg1) 110 | k1, err := basicKind(v1) 111 | if err != nil { 112 | return false, err 113 | } 114 | v2 := reflect.ValueOf(arg2) 115 | k2, err := basicKind(v2) 116 | if err != nil { 117 | return false, err 118 | } 119 | if k1 != k2 { 120 | return false, errBadComparison 121 | } 122 | truth := false 123 | switch k1 { 124 | case boolKind, complexKind: 125 | return false, errBadComparisonType 126 | case floatKind: 127 | truth = v1.Float() < v2.Float() 128 | case intKind: 129 | truth = v1.Int() < v2.Int() 130 | case stringKind: 131 | truth = v1.String() < v2.String() 132 | case uintKind: 133 | truth = v1.Uint() < v2.Uint() 134 | default: 135 | panic("invalid kind") 136 | } 137 | return truth, nil 138 | } 139 | 140 | // le evaluates the comparison <= b. 141 | func le(arg1, arg2 interface{}) (bool, error) { 142 | // <= is < or ==. 143 | lessThan, err := lt(arg1, arg2) 144 | if lessThan || err != nil { 145 | return lessThan, err 146 | } 147 | return eq(arg1, arg2) 148 | } 149 | 150 | // gt evaluates the comparison a > b. 151 | func gt(arg1, arg2 interface{}) (bool, error) { 152 | // > is the inverse of <=. 153 | lessOrEqual, err := le(arg1, arg2) 154 | if err != nil { 155 | return false, err 156 | } 157 | return !lessOrEqual, nil 158 | } 159 | 160 | func getCol(cols map[string]*xorm.Column, name string) *xorm.Column { 161 | return cols[name] 162 | } 163 | 164 | func formatGo(src string) (string, error) { 165 | source, err := format.Source([]byte(src)) 166 | if err != nil { 167 | return "", err 168 | } 169 | return string(source), nil 170 | } 171 | 172 | func genGoImports(tables []*xorm.Table) map[string]string { 173 | imports := make(map[string]string) 174 | 175 | for _, table := range tables { 176 | for _, col := range table.Columns { 177 | if typestring(col) == "time.Time" { 178 | imports["time"] = "time" 179 | } 180 | } 181 | } 182 | return imports 183 | } 184 | 185 | func typestring(col *xorm.Column) string { 186 | st := col.SQLType 187 | /*if col.IsPrimaryKey { 188 | return "int64" 189 | }*/ 190 | t := xorm.SQLType2Type(st) 191 | s := t.String() 192 | if s == "[]uint8" { 193 | return "[]byte" 194 | } 195 | return s 196 | } 197 | 198 | func tag(table *xorm.Table, col *xorm.Column) string { 199 | isNameId := (mapper.Table2Obj(col.Name) == "Id") 200 | isIdPk := isNameId && typestring(col) == "int64" 201 | 202 | res := make([]string, 0) 203 | if !col.Nullable { 204 | if !isIdPk { 205 | res = append(res, "not null") 206 | } 207 | } 208 | if col.IsPrimaryKey { 209 | if !isIdPk { 210 | res = append(res, "pk") 211 | } 212 | } 213 | if col.Default != "" { 214 | res = append(res, "default "+col.Default) 215 | } 216 | if col.IsAutoIncrement { 217 | if !isIdPk { 218 | res = append(res, "autoincr") 219 | } 220 | } 221 | if col.IsCreated { 222 | res = append(res, "created") 223 | } 224 | if col.IsUpdated { 225 | res = append(res, "updated") 226 | } 227 | for name, _ := range col.Indexes { 228 | index := table.Indexes[name] 229 | var uistr string 230 | if index.Type == xorm.UniqueType { 231 | uistr = "unique" 232 | } else if index.Type == xorm.IndexType { 233 | uistr = "index" 234 | } 235 | if len(index.Cols) > 1 { 236 | uistr += "(" + index.Name + ")" 237 | } 238 | res = append(res, uistr) 239 | } 240 | 241 | nstr := col.SQLType.Name 242 | if col.Length != 0 { 243 | if col.Length2 != 0 { 244 | nstr += fmt.Sprintf("(%v,%v)", col.Length, col.Length2) 245 | } else { 246 | nstr += fmt.Sprintf("(%v)", col.Length) 247 | } 248 | } 249 | res = append(res, nstr) 250 | 251 | var tags []string 252 | if genJson { 253 | tags = append(tags, "json:\""+col.Name+"\"") 254 | } 255 | if len(res) > 0 { 256 | tags = append(tags, "xorm:\""+strings.Join(res, " ")+"\"") 257 | } 258 | if len(tags) > 0 { 259 | return "`" + strings.Join(tags, " ") + "`" 260 | } else { 261 | return "" 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /oracle.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type oracle struct { 13 | base 14 | } 15 | 16 | type oracleParser struct { 17 | } 18 | 19 | //dataSourceName=user/password@ipv4:port/dbname 20 | //dataSourceName=user/password@[ipv6]:port/dbname 21 | func (p *oracleParser) parse(driverName, dataSourceName string) (*uri, error) { 22 | db := &uri{dbType: ORACLE_OCI} 23 | dsnPattern := regexp.MustCompile( 24 | `^(?P.*)\/(?P.*)@` + // user:password@ 25 | `(?P.*)` + // ip:port 26 | `\/(?P.*)`) // dbname 27 | matches := dsnPattern.FindStringSubmatch(dataSourceName) 28 | names := dsnPattern.SubexpNames() 29 | for i, match := range matches { 30 | switch names[i] { 31 | case "dbname": 32 | db.dbName = match 33 | } 34 | } 35 | if db.dbName == "" { 36 | return nil, errors.New("dbname is empty") 37 | } 38 | return db, nil 39 | } 40 | 41 | func (db *oracle) Init(drivername, uri string) error { 42 | return db.base.init(&oracleParser{}, drivername, uri) 43 | } 44 | 45 | func (db *oracle) SqlType(c *Column) string { 46 | var res string 47 | switch t := c.SQLType.Name; t { 48 | case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool, Serial, BigSerial: 49 | return "NUMBER" 50 | case Binary, VarBinary, Blob, TinyBlob, MediumBlob, LongBlob, Bytea: 51 | return Blob 52 | case Time, DateTime, TimeStamp: 53 | res = TimeStamp 54 | case TimeStampz: 55 | res = "TIMESTAMP WITH TIME ZONE" 56 | case Float, Double, Numeric, Decimal: 57 | res = "NUMBER" 58 | case Text, MediumText, LongText: 59 | res = "CLOB" 60 | case Char, Varchar, TinyText: 61 | return "VARCHAR2" 62 | default: 63 | res = t 64 | } 65 | 66 | var hasLen1 bool = (c.Length > 0) 67 | var hasLen2 bool = (c.Length2 > 0) 68 | if hasLen1 { 69 | res += "(" + strconv.Itoa(c.Length) + ")" 70 | } else if hasLen2 { 71 | res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" 72 | } 73 | return res 74 | } 75 | 76 | func (db *oracle) SupportInsertMany() bool { 77 | return true 78 | } 79 | 80 | func (db *oracle) QuoteStr() string { 81 | return "\"" 82 | } 83 | 84 | func (db *oracle) AutoIncrStr() string { 85 | return "" 86 | } 87 | 88 | func (db *oracle) SupportEngine() bool { 89 | return false 90 | } 91 | 92 | func (db *oracle) SupportCharset() bool { 93 | return false 94 | } 95 | 96 | func (db *oracle) IndexOnTable() bool { 97 | return false 98 | } 99 | 100 | func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) { 101 | args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(idxName)} 102 | return `SELECT INDEX_NAME FROM USER_INDEXES ` + 103 | `WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args 104 | } 105 | 106 | func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { 107 | args := []interface{}{strings.ToUpper(tableName)} 108 | return `SELECT table_name FROM user_tables WHERE table_name = ?`, args 109 | } 110 | 111 | func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) { 112 | args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(colName)} 113 | return "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" + 114 | " AND column_name = ?", args 115 | } 116 | 117 | func (db *oracle) GetColumns(tableName string) ([]string, map[string]*Column, error) { 118 | args := []interface{}{strings.ToUpper(tableName)} 119 | s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + 120 | "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" 121 | 122 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 123 | if err != nil { 124 | return nil, nil, err 125 | } 126 | defer cnn.Close() 127 | res, err := query(cnn, s, args...) 128 | if err != nil { 129 | return nil, nil, err 130 | } 131 | cols := make(map[string]*Column) 132 | colSeq := make([]string, 0) 133 | for _, record := range res { 134 | col := new(Column) 135 | col.Indexes = make(map[string]bool) 136 | for name, content := range record { 137 | switch name { 138 | case "column_name": 139 | col.Name = strings.Trim(string(content), `" `) 140 | case "data_default": 141 | col.Default = string(content) 142 | if col.Default == "" { 143 | col.DefaultIsEmpty = true 144 | } 145 | case "nullable": 146 | if string(content) == "Y" { 147 | col.Nullable = true 148 | } else { 149 | col.Nullable = false 150 | } 151 | case "data_type": 152 | ct := string(content) 153 | switch ct { 154 | case "VARCHAR2": 155 | col.SQLType = SQLType{Varchar, 0, 0} 156 | case "TIMESTAMP WITH TIME ZONE": 157 | col.SQLType = SQLType{TimeStamp, 0, 0} 158 | default: 159 | col.SQLType = SQLType{strings.ToUpper(ct), 0, 0} 160 | } 161 | if _, ok := sqlTypes[col.SQLType.Name]; !ok { 162 | return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", ct)) 163 | } 164 | case "data_length": 165 | i, err := strconv.Atoi(string(content)) 166 | if err != nil { 167 | return nil, nil, errors.New("retrieve length error") 168 | } 169 | col.Length = i 170 | case "data_precision": 171 | case "data_scale": 172 | } 173 | } 174 | if col.SQLType.IsText() { 175 | if col.Default != "" { 176 | col.Default = "'" + col.Default + "'" 177 | }else{ 178 | if col.DefaultIsEmpty { 179 | col.Default = "''" 180 | } 181 | } 182 | } 183 | cols[col.Name] = col 184 | colSeq = append(colSeq, col.Name) 185 | } 186 | 187 | return colSeq, cols, nil 188 | } 189 | 190 | func (db *oracle) GetTables() ([]*Table, error) { 191 | args := []interface{}{} 192 | s := "SELECT table_name FROM user_tables" 193 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 194 | if err != nil { 195 | return nil, err 196 | } 197 | defer cnn.Close() 198 | res, err := query(cnn, s, args...) 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | tables := make([]*Table, 0) 204 | for _, record := range res { 205 | table := new(Table) 206 | for name, content := range record { 207 | switch name { 208 | case "table_name": 209 | table.Name = string(content) 210 | } 211 | } 212 | tables = append(tables, table) 213 | } 214 | return tables, nil 215 | } 216 | 217 | func (db *oracle) GetIndexes(tableName string) (map[string]*Index, error) { 218 | args := []interface{}{tableName} 219 | s := "SELECT t.column_name,i.table_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " + 220 | "WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =:1" 221 | 222 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 223 | if err != nil { 224 | return nil, err 225 | } 226 | defer cnn.Close() 227 | res, err := query(cnn, s, args...) 228 | if err != nil { 229 | return nil, err 230 | } 231 | 232 | indexes := make(map[string]*Index, 0) 233 | for _, record := range res { 234 | var indexType int 235 | var indexName string 236 | var colName string 237 | 238 | for name, content := range record { 239 | switch name { 240 | case "index_name": 241 | indexName = strings.Trim(string(content), `" `) 242 | case "uniqueness": 243 | c := string(content) 244 | if c == "UNIQUE" { 245 | indexType = UniqueType 246 | } else { 247 | indexType = IndexType 248 | } 249 | case "column_name": 250 | colName = string(content) 251 | } 252 | } 253 | 254 | var index *Index 255 | var ok bool 256 | if index, ok = indexes[indexName]; !ok { 257 | index = new(Index) 258 | index.Type = indexType 259 | index.Name = indexName 260 | indexes[indexName] = index 261 | } 262 | index.AddColumn(colName) 263 | } 264 | return indexes, nil 265 | } 266 | -------------------------------------------------------------------------------- /postgres_test.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | _ "github.com/lib/pq" 8 | ) 9 | 10 | //var connStr string = "dbname=xorm_test user=lunny password=1234 sslmode=disable" 11 | 12 | var connStr string = "dbname=xorm_test sslmode=disable" 13 | 14 | func newPostgresEngine() (*Engine, error) { 15 | return NewEngine("postgres", connStr) 16 | } 17 | 18 | func newPostgresDriverDB() (*sql.DB, error) { 19 | return sql.Open("postgres", connStr) 20 | } 21 | 22 | func TestPostgres(t *testing.T) { 23 | engine, err := newPostgresEngine() 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | defer engine.Close() 29 | engine.ShowSQL = showTestSql 30 | engine.ShowErr = showTestSql 31 | engine.ShowWarn = showTestSql 32 | engine.ShowDebug = showTestSql 33 | 34 | testAll(engine, t) 35 | testAll2(engine, t) 36 | testAll3(engine, t) 37 | } 38 | 39 | func TestPostgresWithCache(t *testing.T) { 40 | engine, err := newPostgresEngine() 41 | if err != nil { 42 | t.Error(err) 43 | return 44 | } 45 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 46 | defer engine.Close() 47 | engine.ShowSQL = showTestSql 48 | engine.ShowErr = showTestSql 49 | engine.ShowWarn = showTestSql 50 | engine.ShowDebug = showTestSql 51 | 52 | testAll(engine, t) 53 | testAll2(engine, t) 54 | } 55 | 56 | /* 57 | func TestPostgres2(t *testing.T) { 58 | engine, err := NewEngine("postgres", "dbname=xorm_test sslmode=disable") 59 | if err != nil { 60 | t.Error(err) 61 | return 62 | } 63 | defer engine.Close() 64 | engine.ShowSQL = showTestSql 65 | engine.Mapper = SameMapper{} 66 | 67 | fmt.Println("-------------- directCreateTable --------------") 68 | directCreateTable(engine, t) 69 | fmt.Println("-------------- mapper --------------") 70 | mapper(engine, t) 71 | fmt.Println("-------------- insert --------------") 72 | insert(engine, t) 73 | fmt.Println("-------------- querySameMapper --------------") 74 | querySameMapper(engine, t) 75 | fmt.Println("-------------- execSameMapper --------------") 76 | execSameMapper(engine, t) 77 | fmt.Println("-------------- insertAutoIncr --------------") 78 | insertAutoIncr(engine, t) 79 | fmt.Println("-------------- insertMulti --------------") 80 | insertMulti(engine, t) 81 | fmt.Println("-------------- insertTwoTable --------------") 82 | insertTwoTable(engine, t) 83 | fmt.Println("-------------- updateSameMapper --------------") 84 | updateSameMapper(engine, t) 85 | fmt.Println("-------------- testdelete --------------") 86 | testdelete(engine, t) 87 | fmt.Println("-------------- get --------------") 88 | get(engine, t) 89 | fmt.Println("-------------- cascadeGet --------------") 90 | cascadeGet(engine, t) 91 | fmt.Println("-------------- find --------------") 92 | find(engine, t) 93 | fmt.Println("-------------- find2 --------------") 94 | find2(engine, t) 95 | fmt.Println("-------------- findMap --------------") 96 | findMap(engine, t) 97 | fmt.Println("-------------- findMap2 --------------") 98 | findMap2(engine, t) 99 | fmt.Println("-------------- count --------------") 100 | count(engine, t) 101 | fmt.Println("-------------- where --------------") 102 | where(engine, t) 103 | fmt.Println("-------------- in --------------") 104 | in(engine, t) 105 | fmt.Println("-------------- limit --------------") 106 | limit(engine, t) 107 | fmt.Println("-------------- orderSameMapper --------------") 108 | orderSameMapper(engine, t) 109 | fmt.Println("-------------- joinSameMapper --------------") 110 | joinSameMapper(engine, t) 111 | fmt.Println("-------------- havingSameMapper --------------") 112 | havingSameMapper(engine, t) 113 | fmt.Println("-------------- combineTransactionSameMapper --------------") 114 | combineTransactionSameMapper(engine, t) 115 | fmt.Println("-------------- table --------------") 116 | table(engine, t) 117 | fmt.Println("-------------- createMultiTables --------------") 118 | createMultiTables(engine, t) 119 | fmt.Println("-------------- tableOp --------------") 120 | tableOp(engine, t) 121 | fmt.Println("-------------- testColsSameMapper --------------") 122 | testColsSameMapper(engine, t) 123 | fmt.Println("-------------- testCharst --------------") 124 | testCharst(engine, t) 125 | fmt.Println("-------------- testStoreEngine --------------") 126 | testStoreEngine(engine, t) 127 | fmt.Println("-------------- testExtends --------------") 128 | testExtends(engine, t) 129 | fmt.Println("-------------- testColTypes --------------") 130 | testColTypes(engine, t) 131 | fmt.Println("-------------- testCustomType --------------") 132 | testCustomType(engine, t) 133 | fmt.Println("-------------- testCreatedAndUpdated --------------") 134 | testCreatedAndUpdated(engine, t) 135 | fmt.Println("-------------- testIndexAndUnique --------------") 136 | testIndexAndUnique(engine, t) 137 | fmt.Println("-------------- testMetaInfo --------------") 138 | testMetaInfo(engine, t) 139 | fmt.Println("-------------- testIterate --------------") 140 | testIterate(engine, t) 141 | fmt.Println("-------------- testStrangeName --------------") 142 | testStrangeName(engine, t) 143 | fmt.Println("-------------- testVersion --------------") 144 | testVersion(engine, t) 145 | fmt.Println("-------------- testDistinct --------------") 146 | testDistinct(engine, t) 147 | fmt.Println("-------------- testUseBool --------------") 148 | testUseBool(engine, t) 149 | fmt.Println("-------------- transaction --------------") 150 | transaction(engine, t) 151 | }*/ 152 | 153 | const ( 154 | createTablePostgres = `CREATE TABLE IF NOT EXISTS "big_struct" ("id" SERIAL PRIMARY KEY NOT NULL, "name" VARCHAR(255) NULL, "title" VARCHAR(255) NULL, "age" VARCHAR(255) NULL, "alias" VARCHAR(255) NULL, "nick_name" VARCHAR(255) NULL);` 155 | dropTablePostgres = `DROP TABLE IF EXISTS "big_struct";` 156 | ) 157 | 158 | func BenchmarkPostgresDriverInsert(t *testing.B) { 159 | doBenchDriver(newPostgresDriverDB, createTablePostgres, dropTablePostgres, 160 | doBenchDriverInsert, t) 161 | } 162 | 163 | func BenchmarkPostgresDriverFind(t *testing.B) { 164 | doBenchDriver(newPostgresDriverDB, createTablePostgres, dropTablePostgres, 165 | doBenchDriverFind, t) 166 | } 167 | 168 | func BenchmarkPostgresNoCacheInsert(t *testing.B) { 169 | engine, err := newPostgresEngine() 170 | 171 | defer engine.Close() 172 | if err != nil { 173 | t.Error(err) 174 | return 175 | } 176 | //engine.ShowSQL = true 177 | doBenchInsert(engine, t) 178 | } 179 | 180 | func BenchmarkPostgresNoCacheFind(t *testing.B) { 181 | engine, err := newPostgresEngine() 182 | 183 | defer engine.Close() 184 | if err != nil { 185 | t.Error(err) 186 | return 187 | } 188 | //engine.ShowSQL = true 189 | doBenchFind(engine, t) 190 | } 191 | 192 | func BenchmarkPostgresNoCacheFindPtr(t *testing.B) { 193 | engine, err := newPostgresEngine() 194 | 195 | defer engine.Close() 196 | if err != nil { 197 | t.Error(err) 198 | return 199 | } 200 | //engine.ShowSQL = true 201 | doBenchFindPtr(engine, t) 202 | } 203 | 204 | func BenchmarkPostgresCacheInsert(t *testing.B) { 205 | engine, err := newPostgresEngine() 206 | 207 | defer engine.Close() 208 | if err != nil { 209 | t.Error(err) 210 | return 211 | } 212 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 213 | 214 | doBenchInsert(engine, t) 215 | } 216 | 217 | func BenchmarkPostgresCacheFind(t *testing.B) { 218 | engine, err := newPostgresEngine() 219 | 220 | defer engine.Close() 221 | if err != nil { 222 | t.Error(err) 223 | return 224 | } 225 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 226 | 227 | doBenchFind(engine, t) 228 | } 229 | 230 | func BenchmarkPostgresCacheFindPtr(t *testing.B) { 231 | engine, err := newPostgresEngine() 232 | 233 | defer engine.Close() 234 | if err != nil { 235 | t.Error(err) 236 | return 237 | } 238 | engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) 239 | 240 | doBenchFindPtr(engine, t) 241 | } 242 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | //"fmt" 6 | "sync" 7 | //"sync/atomic" 8 | "container/list" 9 | "reflect" 10 | "time" 11 | ) 12 | 13 | // Interface IConnecPool is a connection pool interface, all implements should implement 14 | // Init, RetrieveDB, ReleaseDB and Close methods. 15 | // Init for init when engine be created or invoke SetPool 16 | // RetrieveDB for requesting a connection to db; 17 | // ReleaseDB for releasing a db connection; 18 | // Close for invoking when engine.Close 19 | type IConnectPool interface { 20 | Init(engine *Engine) error 21 | RetrieveDB(engine *Engine) (*sql.DB, error) 22 | ReleaseDB(engine *Engine, db *sql.DB) 23 | Close(engine *Engine) error 24 | SetMaxIdleConns(conns int) 25 | MaxIdleConns() int 26 | SetMaxConns(conns int) 27 | MaxConns() int 28 | } 29 | 30 | // Struct NoneConnectPool is a implement for IConnectPool. It provides directly invoke driver's 31 | // open and release connection function 32 | type NoneConnectPool struct { 33 | } 34 | 35 | // NewNoneConnectPool new a NoneConnectPool. 36 | func NewNoneConnectPool() IConnectPool { 37 | return &NoneConnectPool{} 38 | } 39 | 40 | // Init do nothing 41 | func (p *NoneConnectPool) Init(engine *Engine) error { 42 | return nil 43 | } 44 | 45 | // RetrieveDB directly open a connection 46 | func (p *NoneConnectPool) RetrieveDB(engine *Engine) (db *sql.DB, err error) { 47 | db, err = engine.OpenDB() 48 | return 49 | } 50 | 51 | // ReleaseDB directly close a connection 52 | func (p *NoneConnectPool) ReleaseDB(engine *Engine, db *sql.DB) { 53 | db.Close() 54 | } 55 | 56 | // Close do nothing 57 | func (p *NoneConnectPool) Close(engine *Engine) error { 58 | return nil 59 | } 60 | 61 | func (p *NoneConnectPool) SetMaxIdleConns(conns int) { 62 | } 63 | 64 | func (p *NoneConnectPool) MaxIdleConns() int { 65 | return 0 66 | } 67 | 68 | // not implemented 69 | func (p *NoneConnectPool) SetMaxConns(conns int) { 70 | } 71 | 72 | // not implemented 73 | func (p *NoneConnectPool) MaxConns() int { 74 | return -1 75 | } 76 | 77 | // Struct SysConnectPool is a simple wrapper for using system default connection pool. 78 | // About the system connection pool, you can review the code database/sql/sql.go 79 | // It's currently default Pool implments. 80 | type SysConnectPool struct { 81 | db *sql.DB 82 | maxIdleConns int 83 | maxConns int 84 | curConns int 85 | mutex *sync.Mutex 86 | queue *list.List 87 | } 88 | 89 | // NewSysConnectPool new a SysConnectPool. 90 | func NewSysConnectPool() IConnectPool { 91 | return &SysConnectPool{} 92 | } 93 | 94 | // Init create a db immediately and keep it util engine closed. 95 | func (s *SysConnectPool) Init(engine *Engine) error { 96 | db, err := engine.OpenDB() 97 | if err != nil { 98 | return err 99 | } 100 | s.db = db 101 | s.maxIdleConns = 2 102 | s.maxConns = -1 103 | s.curConns = 0 104 | s.mutex = &sync.Mutex{} 105 | s.queue = list.New() 106 | return nil 107 | } 108 | 109 | type node struct { 110 | mutex sync.Mutex 111 | cond *sync.Cond 112 | } 113 | 114 | func newCondNode() *node { 115 | n := &node{} 116 | n.cond = sync.NewCond(&n.mutex) 117 | return n 118 | } 119 | 120 | // RetrieveDB just return the only db 121 | func (s *SysConnectPool) RetrieveDB(engine *Engine) (db *sql.DB, err error) { 122 | /*if s.maxConns > 0 { 123 | fmt.Println("before retrieve") 124 | s.mutex.Lock() 125 | for s.curConns >= s.maxConns { 126 | fmt.Println("before waiting...", s.curConns, s.queue.Len()) 127 | s.mutex.Unlock() 128 | n := NewNode() 129 | n.cond.L.Lock() 130 | s.queue.PushBack(n) 131 | n.cond.Wait() 132 | n.cond.L.Unlock() 133 | s.mutex.Lock() 134 | fmt.Println("after waiting...", s.curConns, s.queue.Len()) 135 | } 136 | s.curConns += 1 137 | s.mutex.Unlock() 138 | fmt.Println("after retrieve") 139 | }*/ 140 | return s.db, nil 141 | } 142 | 143 | // ReleaseDB do nothing 144 | func (s *SysConnectPool) ReleaseDB(engine *Engine, db *sql.DB) { 145 | /*if s.maxConns > 0 { 146 | s.mutex.Lock() 147 | fmt.Println("before release", s.queue.Len()) 148 | s.curConns -= 1 149 | 150 | if e := s.queue.Front(); e != nil { 151 | n := e.Value.(*node) 152 | //n.cond.L.Lock() 153 | n.cond.Signal() 154 | fmt.Println("signaled...") 155 | s.queue.Remove(e) 156 | //n.cond.L.Unlock() 157 | } 158 | fmt.Println("after released", s.queue.Len()) 159 | s.mutex.Unlock() 160 | }*/ 161 | } 162 | 163 | // Close closed the only db 164 | func (p *SysConnectPool) Close(engine *Engine) error { 165 | return p.db.Close() 166 | } 167 | 168 | func (p *SysConnectPool) SetMaxIdleConns(conns int) { 169 | p.db.SetMaxIdleConns(conns) 170 | p.maxIdleConns = conns 171 | } 172 | 173 | func (p *SysConnectPool) MaxIdleConns() int { 174 | return p.maxIdleConns 175 | } 176 | 177 | // not implemented 178 | func (p *SysConnectPool) SetMaxConns(conns int) { 179 | p.maxConns = conns 180 | // if support SetMaxOpenConns, go 1.2+, then set 181 | if reflect.ValueOf(p.db).MethodByName("SetMaxOpenConns").IsValid() { 182 | reflect.ValueOf(p.db).MethodByName("SetMaxOpenConns").Call([]reflect.Value{reflect.ValueOf(conns)}) 183 | } 184 | //p.db.SetMaxOpenConns(conns) 185 | } 186 | 187 | // not implemented 188 | func (p *SysConnectPool) MaxConns() int { 189 | return p.maxConns 190 | } 191 | 192 | // NewSimpleConnectPool new a SimpleConnectPool 193 | func NewSimpleConnectPool() IConnectPool { 194 | return &SimpleConnectPool{releasedConnects: make([]*sql.DB, 10), 195 | usingConnects: map[*sql.DB]time.Time{}, 196 | cur: -1, 197 | maxWaitTimeOut: 14400, 198 | maxIdleConns: 10, 199 | mutex: &sync.Mutex{}, 200 | } 201 | } 202 | 203 | // Struct SimpleConnectPool is a simple implementation for IConnectPool. 204 | // It's a custom connection pool and not use system connection pool. 205 | // Opening or Closing a database connection must be enter a lock. 206 | // This implements will be improved in furture. 207 | type SimpleConnectPool struct { 208 | releasedConnects []*sql.DB 209 | cur int 210 | usingConnects map[*sql.DB]time.Time 211 | maxWaitTimeOut int 212 | mutex *sync.Mutex 213 | maxIdleConns int 214 | } 215 | 216 | func (s *SimpleConnectPool) Init(engine *Engine) error { 217 | return nil 218 | } 219 | 220 | // RetrieveDB get a connection from connection pool 221 | func (p *SimpleConnectPool) RetrieveDB(engine *Engine) (*sql.DB, error) { 222 | p.mutex.Lock() 223 | defer p.mutex.Unlock() 224 | var db *sql.DB = nil 225 | var err error = nil 226 | //fmt.Printf("%x, rbegin - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) 227 | if p.cur < 0 { 228 | db, err = engine.OpenDB() 229 | if err != nil { 230 | return nil, err 231 | } 232 | p.usingConnects[db] = time.Now() 233 | } else { 234 | db = p.releasedConnects[p.cur] 235 | p.usingConnects[db] = time.Now() 236 | p.releasedConnects[p.cur] = nil 237 | p.cur = p.cur - 1 238 | } 239 | 240 | //fmt.Printf("%x, rend - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) 241 | return db, nil 242 | } 243 | 244 | // ReleaseDB release a db from connection pool 245 | func (p *SimpleConnectPool) ReleaseDB(engine *Engine, db *sql.DB) { 246 | p.mutex.Lock() 247 | defer p.mutex.Unlock() 248 | //fmt.Printf("%x, lbegin - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) 249 | if p.cur >= p.maxIdleConns-1 { 250 | db.Close() 251 | } else { 252 | p.cur = p.cur + 1 253 | p.releasedConnects[p.cur] = db 254 | } 255 | delete(p.usingConnects, db) 256 | //fmt.Printf("%x, lend - released:%v, using:%v\n", &p, p.cur+1, len(p.usingConnects)) 257 | } 258 | 259 | // Close release all db 260 | func (p *SimpleConnectPool) Close(engine *Engine) error { 261 | p.mutex.Lock() 262 | defer p.mutex.Unlock() 263 | for len(p.releasedConnects) > 0 { 264 | p.releasedConnects[0].Close() 265 | p.releasedConnects = p.releasedConnects[1:] 266 | } 267 | 268 | return nil 269 | } 270 | 271 | func (p *SimpleConnectPool) SetMaxIdleConns(conns int) { 272 | p.maxIdleConns = conns 273 | } 274 | 275 | func (p *SimpleConnectPool) MaxIdleConns() int { 276 | return p.maxIdleConns 277 | } 278 | 279 | // not implemented 280 | func (p *SimpleConnectPool) SetMaxConns(conns int) { 281 | } 282 | 283 | // not implemented 284 | func (p *SimpleConnectPool) MaxConns() int { 285 | return -1 286 | } 287 | -------------------------------------------------------------------------------- /mssql.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | //"crypto/tls" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | //"regexp" 9 | "strconv" 10 | "strings" 11 | //"time" 12 | ) 13 | 14 | type mssql struct { 15 | base 16 | quoteFilter Filter 17 | } 18 | 19 | type odbcParser struct { 20 | } 21 | 22 | func (p *odbcParser) parse(driverName, dataSourceName string) (*uri, error) { 23 | kv := strings.Split(dataSourceName, ";") 24 | var dbName string 25 | 26 | for _, c := range kv { 27 | vv := strings.Split(strings.TrimSpace(c), "=") 28 | if len(vv) == 2 { 29 | switch strings.ToLower(vv[0]) { 30 | case "database": 31 | dbName = vv[1] 32 | } 33 | } 34 | } 35 | if dbName == "" { 36 | return nil, errors.New("no db name provided") 37 | } 38 | return &uri{dbName: dbName, dbType: MSSQL}, nil 39 | } 40 | 41 | func (db *mssql) Init(drivername, uri string) error { 42 | db.quoteFilter = &QuoteFilter{} 43 | return db.base.init(&odbcParser{}, drivername, uri) 44 | } 45 | 46 | func (db *mssql) SqlType(c *Column) string { 47 | var res string 48 | switch t := c.SQLType.Name; t { 49 | case Bool: 50 | res = TinyInt 51 | case Serial: 52 | c.IsAutoIncrement = true 53 | c.IsPrimaryKey = true 54 | c.Nullable = false 55 | res = Int 56 | case BigSerial: 57 | c.IsAutoIncrement = true 58 | c.IsPrimaryKey = true 59 | c.Nullable = false 60 | res = BigInt 61 | case Bytea, Blob, Binary, TinyBlob, MediumBlob, LongBlob: 62 | res = VarBinary 63 | if c.Length == 0 { 64 | c.Length = 50 65 | } 66 | case TimeStamp: 67 | res = DateTime 68 | case TimeStampz: 69 | res = "DATETIMEOFFSET" 70 | c.Length = 7 71 | case MediumInt: 72 | res = Int 73 | case MediumText, TinyText, LongText: 74 | res = Text 75 | case Double: 76 | res = Real 77 | default: 78 | res = t 79 | } 80 | 81 | if res == Int { 82 | return Int 83 | } 84 | 85 | var hasLen1 bool = (c.Length > 0) 86 | var hasLen2 bool = (c.Length2 > 0) 87 | if hasLen1 { 88 | res += "(" + strconv.Itoa(c.Length) + ")" 89 | } else if hasLen2 { 90 | res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" 91 | } 92 | return res 93 | } 94 | 95 | func (db *mssql) SupportInsertMany() bool { 96 | return true 97 | } 98 | 99 | func (db *mssql) QuoteStr() string { 100 | return "\"" 101 | } 102 | 103 | func (db *mssql) SupportEngine() bool { 104 | return false 105 | } 106 | 107 | func (db *mssql) AutoIncrStr() string { 108 | return "IDENTITY" 109 | } 110 | 111 | func (db *mssql) DropTableSql(tableName string) string { 112 | return fmt.Sprintf("IF EXISTS (SELECT * FROM sysobjects WHERE id = "+ 113 | "object_id(N'%s') and OBJECTPROPERTY(id, N'IsUserTable') = 1) "+ 114 | "DROP TABLE \"%s\"", tableName, tableName) 115 | } 116 | 117 | func (db *mssql) SupportCharset() bool { 118 | return false 119 | } 120 | 121 | func (db *mssql) IndexOnTable() bool { 122 | return true 123 | } 124 | 125 | func (db *mssql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { 126 | args := []interface{}{idxName} 127 | sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" 128 | return sql, args 129 | } 130 | 131 | func (db *mssql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { 132 | args := []interface{}{tableName, colName} 133 | sql := `SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "COLUMN_NAME" = ?` 134 | return sql, args 135 | } 136 | 137 | func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { 138 | args := []interface{}{} 139 | sql := "select * from sysobjects where id = object_id(N'" + tableName + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1" 140 | return sql, args 141 | } 142 | 143 | func (db *mssql) GetColumns(tableName string) ([]string, map[string]*Column, error) { 144 | args := []interface{}{} 145 | s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale 146 | from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id 147 | where a.object_id=object_id('` + tableName + `')` 148 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 149 | if err != nil { 150 | return nil, nil, err 151 | } 152 | defer cnn.Close() 153 | res, err := query(cnn, s, args...) 154 | if err != nil { 155 | return nil, nil, err 156 | } 157 | cols := make(map[string]*Column) 158 | colSeq := make([]string, 0) 159 | for _, record := range res { 160 | col := new(Column) 161 | col.Indexes = make(map[string]bool) 162 | for name, content := range record { 163 | switch name { 164 | case "name": 165 | 166 | col.Name = strings.Trim(string(content), "` ") 167 | case "ctype": 168 | ct := strings.ToUpper(string(content)) 169 | switch ct { 170 | case "DATETIMEOFFSET": 171 | col.SQLType = SQLType{TimeStampz, 0, 0} 172 | case "NVARCHAR": 173 | col.SQLType = SQLType{Varchar, 0, 0} 174 | case "IMAGE": 175 | col.SQLType = SQLType{VarBinary, 0, 0} 176 | default: 177 | if _, ok := sqlTypes[ct]; ok { 178 | col.SQLType = SQLType{ct, 0, 0} 179 | } else { 180 | return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v", 181 | ct, tableName, col.Name)) 182 | } 183 | } 184 | 185 | case "max_length": 186 | len1, err := strconv.Atoi(strings.TrimSpace(string(content))) 187 | if err != nil { 188 | return nil, nil, err 189 | } 190 | col.Length = len1 191 | } 192 | } 193 | if col.SQLType.IsText() { 194 | if col.Default != "" { 195 | col.Default = "'" + col.Default + "'" 196 | } else { 197 | if col.DefaultIsEmpty { 198 | col.Default = "''" 199 | } 200 | } 201 | } 202 | cols[col.Name] = col 203 | colSeq = append(colSeq, col.Name) 204 | } 205 | return colSeq, cols, nil 206 | } 207 | 208 | func (db *mssql) GetTables() ([]*Table, error) { 209 | args := []interface{}{} 210 | s := `select name from sysobjects where xtype ='U'` 211 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 212 | if err != nil { 213 | return nil, err 214 | } 215 | defer cnn.Close() 216 | res, err := query(cnn, s, args...) 217 | if err != nil { 218 | return nil, err 219 | } 220 | 221 | tables := make([]*Table, 0) 222 | for _, record := range res { 223 | table := new(Table) 224 | for name, content := range record { 225 | switch name { 226 | case "name": 227 | table.Name = strings.Trim(string(content), "` ") 228 | } 229 | } 230 | tables = append(tables, table) 231 | } 232 | return tables, nil 233 | } 234 | 235 | func (db *mssql) GetIndexes(tableName string) (map[string]*Index, error) { 236 | args := []interface{}{tableName} 237 | s := `SELECT 238 | IXS.NAME AS [INDEX_NAME], 239 | C.NAME AS [COLUMN_NAME], 240 | IXS.is_unique AS [IS_UNIQUE], 241 | CASE IXCS.IS_INCLUDED_COLUMN 242 | WHEN 0 THEN 'NONE' 243 | ELSE 'INCLUDED' END AS [IS_INCLUDED_COLUMN] 244 | FROM SYS.INDEXES IXS 245 | INNER JOIN SYS.INDEX_COLUMNS IXCS 246 | ON IXS.OBJECT_ID=IXCS.OBJECT_ID AND IXS.INDEX_ID = IXCS.INDEX_ID 247 | INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID 248 | AND IXCS.COLUMN_ID=C.COLUMN_ID 249 | WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? 250 | ` 251 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 252 | if err != nil { 253 | return nil, err 254 | } 255 | defer cnn.Close() 256 | res, err := query(cnn, s, args...) 257 | if err != nil { 258 | return nil, err 259 | } 260 | 261 | indexes := make(map[string]*Index, 0) 262 | for _, record := range res { 263 | var indexType int 264 | var indexName, colName string 265 | for name, content := range record { 266 | switch name { 267 | case "IS_UNIQUE": 268 | i, err := strconv.ParseBool(string(content)) 269 | if err != nil { 270 | return nil, err 271 | } 272 | 273 | if i { 274 | indexType = UniqueType 275 | } else { 276 | indexType = IndexType 277 | } 278 | case "INDEX_NAME": 279 | indexName = string(content) 280 | case "COLUMN_NAME": 281 | colName = strings.Trim(string(content), "` ") 282 | } 283 | } 284 | 285 | if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { 286 | indexName = indexName[5+len(tableName) : len(indexName)] 287 | } 288 | 289 | var index *Index 290 | var ok bool 291 | if index, ok = indexes[indexName]; !ok { 292 | index = new(Index) 293 | index.Type = indexType 294 | index.Name = indexName 295 | indexes[indexName] = index 296 | } 297 | index.AddColumn(colName) 298 | } 299 | return indexes, nil 300 | } 301 | -------------------------------------------------------------------------------- /postgres.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type postgres struct { 12 | base 13 | } 14 | 15 | type values map[string]string 16 | 17 | func (vs values) Set(k, v string) { 18 | vs[k] = v 19 | } 20 | 21 | func (vs values) Get(k string) (v string) { 22 | return vs[k] 23 | } 24 | 25 | func errorf(s string, args ...interface{}) { 26 | panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) 27 | } 28 | 29 | func parseOpts(name string, o values) { 30 | if len(name) == 0 { 31 | return 32 | } 33 | 34 | name = strings.TrimSpace(name) 35 | 36 | ps := strings.Split(name, " ") 37 | for _, p := range ps { 38 | kv := strings.Split(p, "=") 39 | if len(kv) < 2 { 40 | errorf("invalid option: %q", p) 41 | } 42 | o.Set(kv[0], kv[1]) 43 | } 44 | } 45 | 46 | type postgresParser struct { 47 | } 48 | 49 | func (p *postgresParser) parse(driverName, dataSourceName string) (*uri, error) { 50 | db := &uri{dbType: POSTGRES} 51 | o := make(values) 52 | parseOpts(dataSourceName, o) 53 | 54 | db.dbName = o.Get("dbname") 55 | if db.dbName == "" { 56 | return nil, errors.New("dbname is empty") 57 | } 58 | return db, nil 59 | } 60 | 61 | func (db *postgres) Init(drivername, uri string) error { 62 | return db.base.init(&postgresParser{}, drivername, uri) 63 | } 64 | 65 | func (db *postgres) SqlType(c *Column) string { 66 | var res string 67 | switch t := c.SQLType.Name; t { 68 | case TinyInt: 69 | res = SmallInt 70 | return res 71 | case MediumInt, Int, Integer: 72 | if c.IsAutoIncrement { 73 | return Serial 74 | } 75 | return Integer 76 | case Serial, BigSerial: 77 | c.IsAutoIncrement = true 78 | c.Nullable = false 79 | res = t 80 | case Binary, VarBinary: 81 | return Bytea 82 | case DateTime: 83 | res = TimeStamp 84 | case TimeStampz: 85 | return "timestamp with time zone" 86 | case Float: 87 | res = Real 88 | case TinyText, MediumText, LongText: 89 | res = Text 90 | case Blob, TinyBlob, MediumBlob, LongBlob: 91 | return Bytea 92 | case Double: 93 | return "DOUBLE PRECISION" 94 | default: 95 | if c.IsAutoIncrement { 96 | return Serial 97 | } 98 | res = t 99 | } 100 | 101 | var hasLen1 bool = (c.Length > 0) 102 | var hasLen2 bool = (c.Length2 > 0) 103 | if hasLen1 { 104 | res += "(" + strconv.Itoa(c.Length) + ")" 105 | } else if hasLen2 { 106 | res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" 107 | } 108 | return res 109 | } 110 | 111 | func (db *postgres) SupportInsertMany() bool { 112 | return true 113 | } 114 | 115 | func (db *postgres) QuoteStr() string { 116 | return "\"" 117 | } 118 | 119 | func (db *postgres) AutoIncrStr() string { 120 | return "" 121 | } 122 | 123 | func (db *postgres) SupportEngine() bool { 124 | return false 125 | } 126 | 127 | func (db *postgres) SupportCharset() bool { 128 | return false 129 | } 130 | 131 | func (db *postgres) IndexOnTable() bool { 132 | return false 133 | } 134 | 135 | func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { 136 | args := []interface{}{tableName, idxName} 137 | return `SELECT indexname FROM pg_indexes ` + 138 | `WHERE tablename = ? AND indexname = ?`, args 139 | } 140 | 141 | func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { 142 | args := []interface{}{tableName} 143 | return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args 144 | } 145 | 146 | func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) { 147 | args := []interface{}{tableName, colName} 148 | return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" + 149 | " AND column_name = ?", args 150 | } 151 | 152 | func (db *postgres) GetColumns(tableName string) ([]string, map[string]*Column, error) { 153 | args := []interface{}{tableName} 154 | s := "SELECT column_name, column_default, is_nullable, data_type, character_maximum_length" + 155 | ", numeric_precision, numeric_precision_radix FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" 156 | 157 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 158 | if err != nil { 159 | return nil, nil, err 160 | } 161 | defer cnn.Close() 162 | res, err := query(cnn, s, args...) 163 | if err != nil { 164 | return nil, nil, err 165 | } 166 | cols := make(map[string]*Column) 167 | colSeq := make([]string, 0) 168 | for _, record := range res { 169 | col := new(Column) 170 | col.Indexes = make(map[string]bool) 171 | for name, content := range record { 172 | switch name { 173 | case "column_name": 174 | col.Name = strings.Trim(string(content), `" `) 175 | case "column_default": 176 | if strings.HasPrefix(string(content), "nextval") { 177 | col.IsPrimaryKey = true 178 | } else { 179 | col.Default = string(content) 180 | if col.Default == "" { 181 | col.DefaultIsEmpty = true 182 | } 183 | } 184 | case "is_nullable": 185 | if string(content) == "YES" { 186 | col.Nullable = true 187 | } else { 188 | col.Nullable = false 189 | } 190 | case "data_type": 191 | ct := string(content) 192 | switch ct { 193 | case "character varying", "character": 194 | col.SQLType = SQLType{Varchar, 0, 0} 195 | case "timestamp without time zone": 196 | col.SQLType = SQLType{DateTime, 0, 0} 197 | case "timestamp with time zone": 198 | col.SQLType = SQLType{TimeStampz, 0, 0} 199 | case "double precision": 200 | col.SQLType = SQLType{Double, 0, 0} 201 | case "boolean": 202 | col.SQLType = SQLType{Bool, 0, 0} 203 | case "time without time zone": 204 | col.SQLType = SQLType{Time, 0, 0} 205 | default: 206 | col.SQLType = SQLType{strings.ToUpper(ct), 0, 0} 207 | } 208 | if _, ok := sqlTypes[col.SQLType.Name]; !ok { 209 | return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", ct)) 210 | } 211 | case "character_maximum_length": 212 | i, err := strconv.Atoi(string(content)) 213 | if err != nil { 214 | return nil, nil, errors.New("retrieve length error") 215 | } 216 | col.Length = i 217 | case "numeric_precision": 218 | case "numeric_precision_radix": 219 | } 220 | } 221 | if col.SQLType.IsText() { 222 | if col.Default != "" { 223 | col.Default = "'" + col.Default + "'" 224 | }else{ 225 | if col.DefaultIsEmpty { 226 | col.Default = "''" 227 | } 228 | } 229 | } 230 | cols[col.Name] = col 231 | colSeq = append(colSeq, col.Name) 232 | } 233 | 234 | return colSeq, cols, nil 235 | } 236 | 237 | func (db *postgres) GetTables() ([]*Table, error) { 238 | args := []interface{}{} 239 | s := "SELECT tablename FROM pg_tables where schemaname = 'public'" 240 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 241 | if err != nil { 242 | return nil, err 243 | } 244 | defer cnn.Close() 245 | res, err := query(cnn, s, args...) 246 | if err != nil { 247 | return nil, err 248 | } 249 | 250 | tables := make([]*Table, 0) 251 | for _, record := range res { 252 | table := new(Table) 253 | for name, content := range record { 254 | switch name { 255 | case "tablename": 256 | table.Name = string(content) 257 | } 258 | } 259 | tables = append(tables, table) 260 | } 261 | return tables, nil 262 | } 263 | 264 | func (db *postgres) GetIndexes(tableName string) (map[string]*Index, error) { 265 | args := []interface{}{tableName} 266 | s := "SELECT tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' and tablename = $1" 267 | 268 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 269 | if err != nil { 270 | return nil, err 271 | } 272 | defer cnn.Close() 273 | res, err := query(cnn, s, args...) 274 | if err != nil { 275 | return nil, err 276 | } 277 | 278 | indexes := make(map[string]*Index, 0) 279 | for _, record := range res { 280 | var indexType int 281 | var indexName string 282 | var colNames []string 283 | 284 | for name, content := range record { 285 | switch name { 286 | case "indexname": 287 | indexName = strings.Trim(string(content), `" `) 288 | case "indexdef": 289 | c := string(content) 290 | if strings.HasPrefix(c, "CREATE UNIQUE INDEX") { 291 | indexType = UniqueType 292 | } else { 293 | indexType = IndexType 294 | } 295 | cs := strings.Split(c, "(") 296 | colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") 297 | } 298 | } 299 | if strings.HasSuffix(indexName, "_pkey") { 300 | continue 301 | } 302 | if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { 303 | newIdxName := indexName[5+len(tableName) : len(indexName)] 304 | if newIdxName != "" { 305 | indexName = newIdxName 306 | } 307 | } 308 | 309 | index := &Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} 310 | for _, colName := range colNames { 311 | index.Cols = append(index.Cols, strings.Trim(colName, `" `)) 312 | } 313 | indexes[index.Name] = index 314 | } 315 | return indexes, nil 316 | } 317 | -------------------------------------------------------------------------------- /mysql.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "crypto/tls" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type uri struct { 15 | dbType string 16 | proto string 17 | host string 18 | port string 19 | dbName string 20 | user string 21 | passwd string 22 | charset string 23 | laddr string 24 | raddr string 25 | timeout time.Duration 26 | } 27 | 28 | type parser interface { 29 | parse(driverName, dataSourceName string) (*uri, error) 30 | } 31 | 32 | type mysqlParser struct { 33 | } 34 | 35 | func (p *mysqlParser) parse(driverName, dataSourceName string) (*uri, error) { 36 | dsnPattern := regexp.MustCompile( 37 | `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] 38 | `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] 39 | `\/(?P.*?)` + // /dbname 40 | `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] 41 | matches := dsnPattern.FindStringSubmatch(dataSourceName) 42 | //tlsConfigRegister := make(map[string]*tls.Config) 43 | names := dsnPattern.SubexpNames() 44 | 45 | uri := &uri{dbType: MYSQL} 46 | 47 | for i, match := range matches { 48 | switch names[i] { 49 | case "dbname": 50 | uri.dbName = match 51 | case "params": 52 | if len(match) > 0 { 53 | kvs := strings.Split(match, "&") 54 | for _, kv := range kvs { 55 | splits := strings.Split(kv, "=") 56 | if len(splits) == 2 { 57 | switch splits[0] { 58 | case "charset": 59 | uri.charset = splits[1] 60 | } 61 | } 62 | } 63 | } 64 | 65 | } 66 | } 67 | return uri, nil 68 | } 69 | 70 | type base struct { 71 | parser parser 72 | driverName string 73 | dataSourceName string 74 | *uri 75 | } 76 | 77 | func (b *base) init(parser parser, drivername, dataSourceName string) (err error) { 78 | b.parser = parser 79 | b.driverName, b.dataSourceName = drivername, dataSourceName 80 | b.uri, err = b.parser.parse(b.driverName, b.dataSourceName) 81 | return 82 | } 83 | 84 | func (b *base) URI() *uri { 85 | return b.uri 86 | } 87 | 88 | func (b *base) DBType() string { 89 | return b.uri.dbType 90 | } 91 | 92 | func (db *base) RollBackStr() string { 93 | return "ROLL BACK" 94 | } 95 | 96 | func (db *base) DropTableSql(tableName string) string { 97 | return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) 98 | } 99 | 100 | type mysql struct { 101 | base 102 | net string 103 | addr string 104 | params map[string]string 105 | loc *time.Location 106 | timeout time.Duration 107 | tls *tls.Config 108 | allowAllFiles bool 109 | allowOldPasswords bool 110 | clientFoundRows bool 111 | } 112 | 113 | func (db *mysql) Init(drivername, uri string) error { 114 | return db.base.init(&mysqlParser{}, drivername, uri) 115 | } 116 | 117 | func (db *mysql) SqlType(c *Column) string { 118 | var res string 119 | switch t := c.SQLType.Name; t { 120 | case Bool: 121 | res = TinyInt 122 | c.Length = 1 123 | case Serial: 124 | c.IsAutoIncrement = true 125 | c.IsPrimaryKey = true 126 | c.Nullable = false 127 | res = Int 128 | case BigSerial: 129 | c.IsAutoIncrement = true 130 | c.IsPrimaryKey = true 131 | c.Nullable = false 132 | res = BigInt 133 | case Bytea: 134 | res = Blob 135 | case TimeStampz: 136 | res = Char 137 | c.Length = 64 138 | default: 139 | res = t 140 | } 141 | 142 | var hasLen1 bool = (c.Length > 0) 143 | var hasLen2 bool = (c.Length2 > 0) 144 | if hasLen1 { 145 | res += "(" + strconv.Itoa(c.Length) + ")" 146 | } else if hasLen2 { 147 | res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" 148 | } 149 | return res 150 | } 151 | 152 | func (db *mysql) SupportInsertMany() bool { 153 | return true 154 | } 155 | 156 | func (db *mysql) QuoteStr() string { 157 | return "`" 158 | } 159 | 160 | func (db *mysql) SupportEngine() bool { 161 | return true 162 | } 163 | 164 | func (db *mysql) AutoIncrStr() string { 165 | return "AUTO_INCREMENT" 166 | } 167 | 168 | func (db *mysql) SupportCharset() bool { 169 | return true 170 | } 171 | 172 | func (db *mysql) IndexOnTable() bool { 173 | return true 174 | } 175 | 176 | func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { 177 | args := []interface{}{db.dbName, tableName, idxName} 178 | sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" 179 | sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?" 180 | return sql, args 181 | } 182 | 183 | func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { 184 | args := []interface{}{db.dbName, tableName, colName} 185 | sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" 186 | return sql, args 187 | } 188 | 189 | func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) { 190 | args := []interface{}{db.dbName, tableName} 191 | sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" 192 | return sql, args 193 | } 194 | 195 | func (db *mysql) GetColumns(tableName string) ([]string, map[string]*Column, error) { 196 | args := []interface{}{db.dbName, tableName} 197 | s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + 198 | " `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" 199 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 200 | if err != nil { 201 | return nil, nil, err 202 | } 203 | defer cnn.Close() 204 | res, err := query(cnn, s, args...) 205 | if err != nil { 206 | return nil, nil, err 207 | } 208 | cols := make(map[string]*Column) 209 | colSeq := make([]string, 0) 210 | for _, record := range res { 211 | col := new(Column) 212 | col.Indexes = make(map[string]bool) 213 | for name, content := range record { 214 | switch name { 215 | case "COLUMN_NAME": 216 | col.Name = strings.Trim(string(content), "` ") 217 | case "IS_NULLABLE": 218 | if "YES" == string(content) { 219 | col.Nullable = true 220 | } 221 | case "COLUMN_DEFAULT": 222 | // add '' 223 | col.Default = string(content) 224 | if col.Default == "" { 225 | col.DefaultIsEmpty = true 226 | } 227 | case "COLUMN_TYPE": 228 | cts := strings.Split(string(content), "(") 229 | var len1, len2 int 230 | if len(cts) == 2 { 231 | idx := strings.Index(cts[1], ")") 232 | lens := strings.Split(cts[1][0:idx], ",") 233 | len1, err = strconv.Atoi(strings.TrimSpace(lens[0])) 234 | if err != nil { 235 | return nil, nil, err 236 | } 237 | if len(lens) == 2 { 238 | len2, err = strconv.Atoi(lens[1]) 239 | if err != nil { 240 | return nil, nil, err 241 | } 242 | } 243 | } 244 | colName := cts[0] 245 | colType := strings.ToUpper(colName) 246 | col.Length = len1 247 | col.Length2 = len2 248 | if _, ok := sqlTypes[colType]; ok { 249 | col.SQLType = SQLType{colType, len1, len2} 250 | } else { 251 | return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType)) 252 | } 253 | case "COLUMN_KEY": 254 | key := string(content) 255 | if key == "PRI" { 256 | col.IsPrimaryKey = true 257 | } 258 | if key == "UNI" { 259 | //col.is 260 | } 261 | case "EXTRA": 262 | extra := string(content) 263 | if extra == "auto_increment" { 264 | col.IsAutoIncrement = true 265 | } 266 | } 267 | } 268 | if col.SQLType.IsText() { 269 | if col.Default != "" { 270 | col.Default = "'" + col.Default + "'" 271 | } else { 272 | if col.DefaultIsEmpty { 273 | col.Default = "''" 274 | } 275 | } 276 | } 277 | cols[col.Name] = col 278 | colSeq = append(colSeq, col.Name) 279 | } 280 | return colSeq, cols, nil 281 | } 282 | 283 | func (db *mysql) GetTables() ([]*Table, error) { 284 | args := []interface{}{db.dbName} 285 | s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=?" 286 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 287 | if err != nil { 288 | return nil, err 289 | } 290 | defer cnn.Close() 291 | res, err := query(cnn, s, args...) 292 | if err != nil { 293 | return nil, err 294 | } 295 | 296 | tables := make([]*Table, 0) 297 | for _, record := range res { 298 | table := new(Table) 299 | for name, content := range record { 300 | switch name { 301 | case "TABLE_NAME": 302 | table.Name = strings.Trim(string(content), "` ") 303 | case "ENGINE": 304 | } 305 | } 306 | tables = append(tables, table) 307 | } 308 | return tables, nil 309 | } 310 | 311 | func (db *mysql) GetIndexes(tableName string) (map[string]*Index, error) { 312 | args := []interface{}{db.dbName, tableName} 313 | s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" 314 | cnn, err := sql.Open(db.driverName, db.dataSourceName) 315 | if err != nil { 316 | return nil, err 317 | } 318 | defer cnn.Close() 319 | res, err := query(cnn, s, args...) 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | indexes := make(map[string]*Index, 0) 325 | for _, record := range res { 326 | var indexType int 327 | var indexName, colName string 328 | for name, content := range record { 329 | switch name { 330 | case "NON_UNIQUE": 331 | if "YES" == string(content) || string(content) == "1" { 332 | indexType = IndexType 333 | } else { 334 | indexType = UniqueType 335 | } 336 | case "INDEX_NAME": 337 | indexName = string(content) 338 | case "COLUMN_NAME": 339 | colName = strings.Trim(string(content), "` ") 340 | } 341 | } 342 | if indexName == "PRIMARY" { 343 | continue 344 | } 345 | if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { 346 | indexName = indexName[5+len(tableName) : len(indexName)] 347 | } 348 | 349 | var index *Index 350 | var ok bool 351 | if index, ok = indexes[indexName]; !ok { 352 | index = new(Index) 353 | index.Type = indexType 354 | index.Name = indexName 355 | indexes[indexName] = index 356 | } 357 | index.AddColumn(colName) 358 | } 359 | return indexes, nil 360 | } 361 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "container/list" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | const ( 14 | // default cache expired time 15 | CacheExpired = 60 * time.Minute 16 | // not use now 17 | CacheMaxMemory = 256 18 | // evey ten minutes to clear all expired nodes 19 | CacheGcInterval = 10 * time.Minute 20 | // each time when gc to removed max nodes 21 | CacheGcMaxRemoved = 20 22 | ) 23 | 24 | // CacheStore is a interface to store cache 25 | type CacheStore interface { 26 | Put(key, value interface{}) error 27 | Get(key interface{}) (interface{}, error) 28 | Del(key interface{}) error 29 | } 30 | 31 | // MemoryStore implements CacheStore provide local machine 32 | // memory store 33 | type MemoryStore struct { 34 | store map[interface{}]interface{} 35 | mutex sync.RWMutex 36 | } 37 | 38 | func NewMemoryStore() *MemoryStore { 39 | return &MemoryStore{store: make(map[interface{}]interface{})} 40 | } 41 | 42 | func (s *MemoryStore) Put(key, value interface{}) error { 43 | s.mutex.Lock() 44 | defer s.mutex.Unlock() 45 | s.store[key] = value 46 | return nil 47 | } 48 | 49 | func (s *MemoryStore) Get(key interface{}) (interface{}, error) { 50 | s.mutex.RLock() 51 | defer s.mutex.RUnlock() 52 | if v, ok := s.store[key]; ok { 53 | return v, nil 54 | } 55 | 56 | return nil, ErrNotExist 57 | } 58 | 59 | func (s *MemoryStore) Del(key interface{}) error { 60 | s.mutex.Lock() 61 | defer s.mutex.Unlock() 62 | delete(s.store, key) 63 | return nil 64 | } 65 | 66 | // Cacher is an interface to provide cache 67 | type Cacher interface { 68 | GetIds(tableName, sql string) interface{} 69 | GetBean(tableName string, id int64) interface{} 70 | PutIds(tableName, sql string, ids interface{}) 71 | PutBean(tableName string, id int64, obj interface{}) 72 | DelIds(tableName, sql string) 73 | DelBean(tableName string, id int64) 74 | ClearIds(tableName string) 75 | ClearBeans(tableName string) 76 | } 77 | 78 | type idNode struct { 79 | tbName string 80 | id int64 81 | lastVisit time.Time 82 | } 83 | 84 | type sqlNode struct { 85 | tbName string 86 | sql string 87 | lastVisit time.Time 88 | } 89 | 90 | func newIdNode(tbName string, id int64) *idNode { 91 | return &idNode{tbName, id, time.Now()} 92 | } 93 | 94 | func newSqlNode(tbName, sql string) *sqlNode { 95 | return &sqlNode{tbName, sql, time.Now()} 96 | } 97 | 98 | // LRUCacher implements Cacher according to LRU algorithm 99 | type LRUCacher struct { 100 | idList *list.List 101 | sqlList *list.List 102 | idIndex map[string]map[interface{}]*list.Element 103 | sqlIndex map[string]map[interface{}]*list.Element 104 | store CacheStore 105 | Max int 106 | mutex sync.Mutex 107 | Expired time.Duration 108 | maxSize int 109 | GcInterval time.Duration 110 | } 111 | 112 | func newLRUCacher(store CacheStore, expired time.Duration, maxSize int, max int) *LRUCacher { 113 | cacher := &LRUCacher{store: store, idList: list.New(), 114 | sqlList: list.New(), Expired: expired, maxSize: maxSize, 115 | GcInterval: CacheGcInterval, Max: max, 116 | sqlIndex: make(map[string]map[interface{}]*list.Element), 117 | idIndex: make(map[string]map[interface{}]*list.Element), 118 | } 119 | cacher.RunGC() 120 | return cacher 121 | } 122 | 123 | func NewLRUCacher(store CacheStore, max int) *LRUCacher { 124 | return newLRUCacher(store, CacheExpired, CacheMaxMemory, max) 125 | } 126 | 127 | func NewLRUCacher2(store CacheStore, expired time.Duration, max int) *LRUCacher { 128 | return newLRUCacher(store, expired, 0, max) 129 | } 130 | 131 | //func NewLRUCacher3(store CacheStore, expired time.Duration, maxSize int) *LRUCacher { 132 | // return newLRUCacher(store, expired, maxSize, 0) 133 | //} 134 | 135 | // RunGC run once every m.GcInterval 136 | func (m *LRUCacher) RunGC() { 137 | time.AfterFunc(m.GcInterval, func() { 138 | m.RunGC() 139 | m.GC() 140 | }) 141 | } 142 | 143 | // GC check ids lit and sql list to remove all element expired 144 | func (m *LRUCacher) GC() { 145 | //fmt.Println("begin gc ...") 146 | //defer fmt.Println("end gc ...") 147 | m.mutex.Lock() 148 | defer m.mutex.Unlock() 149 | var removedNum int 150 | for e := m.idList.Front(); e != nil; { 151 | if removedNum <= CacheGcMaxRemoved && 152 | time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { 153 | removedNum++ 154 | next := e.Next() 155 | //fmt.Println("removing ...", e.Value) 156 | node := e.Value.(*idNode) 157 | m.delBean(node.tbName, node.id) 158 | e = next 159 | } else { 160 | //fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.idList.Len()) 161 | break 162 | } 163 | } 164 | 165 | removedNum = 0 166 | for e := m.sqlList.Front(); e != nil; { 167 | if removedNum <= CacheGcMaxRemoved && 168 | time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { 169 | removedNum++ 170 | next := e.Next() 171 | //fmt.Println("removing ...", e.Value) 172 | node := e.Value.(*sqlNode) 173 | m.delIds(node.tbName, node.sql) 174 | e = next 175 | } else { 176 | //fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.sqlList.Len()) 177 | break 178 | } 179 | } 180 | } 181 | 182 | // Get all bean's ids according to sql and parameter from cache 183 | func (m *LRUCacher) GetIds(tableName, sql string) interface{} { 184 | m.mutex.Lock() 185 | defer m.mutex.Unlock() 186 | if _, ok := m.sqlIndex[tableName]; !ok { 187 | m.sqlIndex[tableName] = make(map[interface{}]*list.Element) 188 | } 189 | if v, err := m.store.Get(sql); err == nil { 190 | if el, ok := m.sqlIndex[tableName][sql]; !ok { 191 | el = m.sqlList.PushBack(newSqlNode(tableName, sql)) 192 | m.sqlIndex[tableName][sql] = el 193 | } else { 194 | lastTime := el.Value.(*sqlNode).lastVisit 195 | // if expired, remove the node and return nil 196 | if time.Now().Sub(lastTime) > m.Expired { 197 | m.delIds(tableName, sql) 198 | return nil 199 | } 200 | m.sqlList.MoveToBack(el) 201 | el.Value.(*sqlNode).lastVisit = time.Now() 202 | } 203 | return v 204 | } else { 205 | m.delIds(tableName, sql) 206 | } 207 | 208 | return nil 209 | } 210 | 211 | // Get bean according tableName and id from cache 212 | func (m *LRUCacher) GetBean(tableName string, id int64) interface{} { 213 | m.mutex.Lock() 214 | defer m.mutex.Unlock() 215 | if _, ok := m.idIndex[tableName]; !ok { 216 | m.idIndex[tableName] = make(map[interface{}]*list.Element) 217 | } 218 | tid := genId(tableName, id) 219 | if v, err := m.store.Get(tid); err == nil { 220 | if el, ok := m.idIndex[tableName][id]; ok { 221 | lastTime := el.Value.(*idNode).lastVisit 222 | // if expired, remove the node and return nil 223 | if time.Now().Sub(lastTime) > m.Expired { 224 | m.delBean(tableName, id) 225 | //m.clearIds(tableName) 226 | return nil 227 | } 228 | m.idList.MoveToBack(el) 229 | el.Value.(*idNode).lastVisit = time.Now() 230 | } else { 231 | el = m.idList.PushBack(newIdNode(tableName, id)) 232 | m.idIndex[tableName][id] = el 233 | } 234 | return v 235 | } else { 236 | // store bean is not exist, then remove memory's index 237 | m.delBean(tableName, id) 238 | //m.clearIds(tableName) 239 | return nil 240 | } 241 | } 242 | 243 | // Clear all sql-ids mapping on table tableName from cache 244 | func (m *LRUCacher) clearIds(tableName string) { 245 | if tis, ok := m.sqlIndex[tableName]; ok { 246 | for sql, v := range tis { 247 | m.sqlList.Remove(v) 248 | m.store.Del(sql) 249 | } 250 | } 251 | m.sqlIndex[tableName] = make(map[interface{}]*list.Element) 252 | } 253 | 254 | func (m *LRUCacher) ClearIds(tableName string) { 255 | m.mutex.Lock() 256 | defer m.mutex.Unlock() 257 | m.clearIds(tableName) 258 | } 259 | 260 | func (m *LRUCacher) clearBeans(tableName string) { 261 | if tis, ok := m.idIndex[tableName]; ok { 262 | for id, v := range tis { 263 | m.idList.Remove(v) 264 | tid := genId(tableName, id.(int64)) 265 | m.store.Del(tid) 266 | } 267 | } 268 | m.idIndex[tableName] = make(map[interface{}]*list.Element) 269 | } 270 | 271 | func (m *LRUCacher) ClearBeans(tableName string) { 272 | m.mutex.Lock() 273 | defer m.mutex.Unlock() 274 | m.clearBeans(tableName) 275 | } 276 | 277 | func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { 278 | m.mutex.Lock() 279 | defer m.mutex.Unlock() 280 | if _, ok := m.sqlIndex[tableName]; !ok { 281 | m.sqlIndex[tableName] = make(map[interface{}]*list.Element) 282 | } 283 | if el, ok := m.sqlIndex[tableName][sql]; !ok { 284 | el = m.sqlList.PushBack(newSqlNode(tableName, sql)) 285 | m.sqlIndex[tableName][sql] = el 286 | } else { 287 | el.Value.(*sqlNode).lastVisit = time.Now() 288 | } 289 | m.store.Put(sql, ids) 290 | if m.sqlList.Len() > m.Max { 291 | e := m.sqlList.Front() 292 | node := e.Value.(*sqlNode) 293 | m.delIds(node.tbName, node.sql) 294 | } 295 | } 296 | 297 | func (m *LRUCacher) PutBean(tableName string, id int64, obj interface{}) { 298 | m.mutex.Lock() 299 | defer m.mutex.Unlock() 300 | var el *list.Element 301 | var ok bool 302 | 303 | if el, ok = m.idIndex[tableName][id]; !ok { 304 | el = m.idList.PushBack(newIdNode(tableName, id)) 305 | m.idIndex[tableName][id] = el 306 | } else { 307 | el.Value.(*idNode).lastVisit = time.Now() 308 | } 309 | 310 | m.store.Put(genId(tableName, id), obj) 311 | if m.idList.Len() > m.Max { 312 | e := m.idList.Front() 313 | node := e.Value.(*idNode) 314 | m.delBean(node.tbName, node.id) 315 | } 316 | } 317 | 318 | func (m *LRUCacher) delIds(tableName, sql string) { 319 | if _, ok := m.sqlIndex[tableName]; ok { 320 | if el, ok := m.sqlIndex[tableName][sql]; ok { 321 | delete(m.sqlIndex[tableName], sql) 322 | m.sqlList.Remove(el) 323 | } 324 | } 325 | m.store.Del(sql) 326 | } 327 | 328 | func (m *LRUCacher) DelIds(tableName, sql string) { 329 | m.mutex.Lock() 330 | defer m.mutex.Unlock() 331 | m.delIds(tableName, sql) 332 | } 333 | 334 | func (m *LRUCacher) delBean(tableName string, id int64) { 335 | tid := genId(tableName, id) 336 | if el, ok := m.idIndex[tableName][id]; ok { 337 | delete(m.idIndex[tableName], id) 338 | m.idList.Remove(el) 339 | m.clearIds(tableName) 340 | } 341 | m.store.Del(tid) 342 | } 343 | 344 | func (m *LRUCacher) DelBean(tableName string, id int64) { 345 | m.mutex.Lock() 346 | defer m.mutex.Unlock() 347 | m.delBean(tableName, id) 348 | } 349 | 350 | func encodeIds(ids []int64) (s string) { 351 | s = "[" 352 | for _, id := range ids { 353 | s += fmt.Sprintf("%v,", id) 354 | } 355 | s = s[:len(s)-1] + "]" 356 | return 357 | } 358 | 359 | func decodeIds(s string) []int64 { 360 | res := make([]int64, 0) 361 | if len(s) >= 2 { 362 | ss := strings.Split(s[1:len(s)-1], ",") 363 | for _, s := range ss { 364 | i, err := strconv.ParseInt(s, 10, 64) 365 | if err != nil { 366 | return res 367 | } 368 | res = append(res, i) 369 | } 370 | } 371 | return res 372 | } 373 | 374 | func getCacheSql(m Cacher, tableName, sql string, args interface{}) ([]int64, error) { 375 | bytes := m.GetIds(tableName, genSqlKey(sql, args)) 376 | if bytes == nil { 377 | return nil, errors.New("Not Exist") 378 | } 379 | objs := decodeIds(bytes.(string)) 380 | return objs, nil 381 | } 382 | 383 | func putCacheSql(m Cacher, ids []int64, tableName, sql string, args interface{}) error { 384 | bytes := encodeIds(ids) 385 | m.PutIds(tableName, genSqlKey(sql, args), bytes) 386 | return nil 387 | } 388 | 389 | func genSqlKey(sql string, args interface{}) string { 390 | return fmt.Sprintf("%v-%v", sql, args) 391 | } 392 | 393 | func genId(prefix string, id int64) string { 394 | return fmt.Sprintf("%v-%v", prefix, id) 395 | } 396 | -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // xorm SQL types 11 | type SQLType struct { 12 | Name string 13 | DefaultLength int 14 | DefaultLength2 int 15 | } 16 | 17 | func (s *SQLType) IsText() bool { 18 | return s.Name == Char || s.Name == Varchar || s.Name == TinyText || 19 | s.Name == Text || s.Name == MediumText || s.Name == LongText 20 | } 21 | 22 | func (s *SQLType) IsBlob() bool { 23 | return (s.Name == TinyBlob) || (s.Name == Blob) || 24 | s.Name == MediumBlob || s.Name == LongBlob || 25 | s.Name == Binary || s.Name == VarBinary || s.Name == Bytea 26 | } 27 | 28 | const () 29 | 30 | var ( 31 | Bit = "BIT" 32 | TinyInt = "TINYINT" 33 | SmallInt = "SMALLINT" 34 | MediumInt = "MEDIUMINT" 35 | Int = "INT" 36 | Integer = "INTEGER" 37 | BigInt = "BIGINT" 38 | 39 | Char = "CHAR" 40 | Varchar = "VARCHAR" 41 | TinyText = "TINYTEXT" 42 | Text = "TEXT" 43 | MediumText = "MEDIUMTEXT" 44 | LongText = "LONGTEXT" 45 | 46 | Date = "DATE" 47 | DateTime = "DATETIME" 48 | Time = "TIME" 49 | TimeStamp = "TIMESTAMP" 50 | TimeStampz = "TIMESTAMPZ" 51 | 52 | Decimal = "DECIMAL" 53 | Numeric = "NUMERIC" 54 | 55 | Real = "REAL" 56 | Float = "FLOAT" 57 | Double = "DOUBLE" 58 | 59 | Binary = "BINARY" 60 | VarBinary = "VARBINARY" 61 | TinyBlob = "TINYBLOB" 62 | Blob = "BLOB" 63 | MediumBlob = "MEDIUMBLOB" 64 | LongBlob = "LONGBLOB" 65 | Bytea = "BYTEA" 66 | 67 | Bool = "BOOL" 68 | 69 | Serial = "SERIAL" 70 | BigSerial = "BIGSERIAL" 71 | 72 | sqlTypes = map[string]bool{ 73 | Bit: true, 74 | TinyInt: true, 75 | SmallInt: true, 76 | MediumInt: true, 77 | Int: true, 78 | Integer: true, 79 | BigInt: true, 80 | 81 | Char: true, 82 | Varchar: true, 83 | TinyText: true, 84 | Text: true, 85 | MediumText: true, 86 | LongText: true, 87 | 88 | Date: true, 89 | DateTime: true, 90 | Time: true, 91 | TimeStamp: true, 92 | TimeStampz: true, 93 | 94 | Decimal: true, 95 | Numeric: true, 96 | 97 | Binary: true, 98 | VarBinary: true, 99 | Real: true, 100 | Float: true, 101 | Double: true, 102 | TinyBlob: true, 103 | Blob: true, 104 | MediumBlob: true, 105 | LongBlob: true, 106 | Bytea: true, 107 | 108 | Bool: true, 109 | 110 | Serial: true, 111 | BigSerial: true, 112 | } 113 | 114 | intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"} 115 | uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"} 116 | ) 117 | 118 | // !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparision 119 | var ( 120 | c_EMPTY_STRING string 121 | c_BOOL_DEFAULT bool 122 | c_BYTE_DEFAULT byte 123 | c_COMPLEX64_DEFAULT complex64 124 | c_COMPLEX128_DEFAULT complex128 125 | c_FLOAT32_DEFAULT float32 126 | c_FLOAT64_DEFAULT float64 127 | c_INT64_DEFAULT int64 128 | c_UINT64_DEFAULT uint64 129 | c_INT32_DEFAULT int32 130 | c_UINT32_DEFAULT uint32 131 | c_INT16_DEFAULT int16 132 | c_UINT16_DEFAULT uint16 133 | c_INT8_DEFAULT int8 134 | c_UINT8_DEFAULT uint8 135 | c_INT_DEFAULT int 136 | c_UINT_DEFAULT uint 137 | c_TIME_DEFAULT time.Time 138 | ) 139 | 140 | func Type2SQLType(t reflect.Type) (st SQLType) { 141 | switch k := t.Kind(); k { 142 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 143 | st = SQLType{Int, 0, 0} 144 | case reflect.Int64, reflect.Uint64: 145 | st = SQLType{BigInt, 0, 0} 146 | case reflect.Float32: 147 | st = SQLType{Float, 0, 0} 148 | case reflect.Float64: 149 | st = SQLType{Double, 0, 0} 150 | case reflect.Complex64, reflect.Complex128: 151 | st = SQLType{Varchar, 64, 0} 152 | case reflect.Array, reflect.Slice, reflect.Map: 153 | if t.Elem() == reflect.TypeOf(c_BYTE_DEFAULT) { 154 | st = SQLType{Blob, 0, 0} 155 | } else { 156 | st = SQLType{Text, 0, 0} 157 | } 158 | case reflect.Bool: 159 | st = SQLType{Bool, 0, 0} 160 | case reflect.String: 161 | st = SQLType{Varchar, 255, 0} 162 | case reflect.Struct: 163 | if t == reflect.TypeOf(c_TIME_DEFAULT) { 164 | st = SQLType{DateTime, 0, 0} 165 | } else { 166 | // TODO need to handle association struct 167 | st = SQLType{Text, 0, 0} 168 | } 169 | case reflect.Ptr: 170 | st, _ = ptrType2SQLType(t) 171 | default: 172 | st = SQLType{Text, 0, 0} 173 | } 174 | return 175 | } 176 | 177 | func ptrType2SQLType(t reflect.Type) (st SQLType, has bool) { 178 | has = true 179 | 180 | switch t { 181 | case reflect.TypeOf(&c_EMPTY_STRING): 182 | st = SQLType{Varchar, 255, 0} 183 | return 184 | case reflect.TypeOf(&c_BOOL_DEFAULT): 185 | st = SQLType{Bool, 0, 0} 186 | case reflect.TypeOf(&c_COMPLEX64_DEFAULT), reflect.TypeOf(&c_COMPLEX128_DEFAULT): 187 | st = SQLType{Varchar, 64, 0} 188 | case reflect.TypeOf(&c_FLOAT32_DEFAULT): 189 | st = SQLType{Float, 0, 0} 190 | case reflect.TypeOf(&c_FLOAT64_DEFAULT): 191 | st = SQLType{Double, 0, 0} 192 | case reflect.TypeOf(&c_INT64_DEFAULT), reflect.TypeOf(&c_UINT64_DEFAULT): 193 | st = SQLType{BigInt, 0, 0} 194 | case reflect.TypeOf(&c_TIME_DEFAULT): 195 | st = SQLType{DateTime, 0, 0} 196 | case reflect.TypeOf(&c_INT_DEFAULT), reflect.TypeOf(&c_INT32_DEFAULT), reflect.TypeOf(&c_INT8_DEFAULT), reflect.TypeOf(&c_INT16_DEFAULT), reflect.TypeOf(&c_UINT_DEFAULT), reflect.TypeOf(&c_UINT32_DEFAULT), reflect.TypeOf(&c_UINT8_DEFAULT), reflect.TypeOf(&c_UINT16_DEFAULT): 197 | st = SQLType{Int, 0, 0} 198 | default: 199 | has = false 200 | } 201 | return 202 | } 203 | 204 | // default sql type change to go types 205 | func SQLType2Type(st SQLType) reflect.Type { 206 | name := strings.ToUpper(st.Name) 207 | switch name { 208 | case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial: 209 | return reflect.TypeOf(1) 210 | case BigInt, BigSerial: 211 | return reflect.TypeOf(int64(1)) 212 | case Float, Real: 213 | return reflect.TypeOf(float32(1)) 214 | case Double: 215 | return reflect.TypeOf(float64(1)) 216 | case Char, Varchar, TinyText, Text, MediumText, LongText: 217 | return reflect.TypeOf("") 218 | case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: 219 | return reflect.TypeOf([]byte{}) 220 | case Bool: 221 | return reflect.TypeOf(true) 222 | case DateTime, Date, Time, TimeStamp, TimeStampz: 223 | return reflect.TypeOf(c_TIME_DEFAULT) 224 | case Decimal, Numeric: 225 | return reflect.TypeOf("") 226 | default: 227 | return reflect.TypeOf("") 228 | } 229 | } 230 | 231 | const ( 232 | IndexType = iota + 1 233 | UniqueType 234 | ) 235 | 236 | // database index 237 | type Index struct { 238 | Name string 239 | Type int 240 | Cols []string 241 | } 242 | 243 | // add columns which will be composite index 244 | func (index *Index) AddColumn(cols ...string) { 245 | for _, col := range cols { 246 | index.Cols = append(index.Cols, col) 247 | } 248 | } 249 | 250 | // new an index 251 | func NewIndex(name string, indexType int) *Index { 252 | return &Index{name, indexType, make([]string, 0)} 253 | } 254 | 255 | const ( 256 | TWOSIDES = iota + 1 257 | ONLYTODB 258 | ONLYFROMDB 259 | ) 260 | 261 | // database column 262 | type Column struct { 263 | Name string 264 | FieldName string 265 | SQLType SQLType 266 | Length int 267 | Length2 int 268 | Nullable bool 269 | Default string 270 | Indexes map[string]bool 271 | IsPrimaryKey bool 272 | IsAutoIncrement bool 273 | MapType int 274 | IsCreated bool 275 | IsUpdated bool 276 | IsCascade bool 277 | IsVersion bool 278 | DefaultIsEmpty bool 279 | } 280 | 281 | // generate column description string according dialect 282 | func (col *Column) String(d dialect) string { 283 | sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " 284 | 285 | sql += d.SqlType(col) + " " 286 | 287 | if col.IsPrimaryKey { 288 | sql += "PRIMARY KEY " 289 | if col.IsAutoIncrement { 290 | sql += d.AutoIncrStr() + " " 291 | } 292 | } 293 | 294 | if col.Nullable { 295 | sql += "NULL " 296 | } else { 297 | sql += "NOT NULL " 298 | } 299 | 300 | if col.Default != "" { 301 | sql += "DEFAULT " + col.Default + " " 302 | } else if col.IsVersion { 303 | sql += "DEFAULT 1 " 304 | } 305 | 306 | return sql 307 | } 308 | 309 | func (col *Column) stringNoPk(d dialect) string { 310 | sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " 311 | 312 | sql += d.SqlType(col) + " " 313 | 314 | if col.Nullable { 315 | sql += "NULL " 316 | } else { 317 | sql += "NOT NULL " 318 | } 319 | 320 | if col.Default != "" { 321 | sql += "DEFAULT " + col.Default + " " 322 | } else if col.IsVersion { 323 | sql += "DEFAULT 1 " 324 | } 325 | 326 | return sql 327 | } 328 | 329 | // return col's filed of struct's value 330 | func (col *Column) ValueOf(bean interface{}) reflect.Value { 331 | var fieldValue reflect.Value 332 | if strings.Contains(col.FieldName, ".") { 333 | fields := strings.Split(col.FieldName, ".") 334 | if len(fields) > 2 { 335 | return reflect.ValueOf(nil) 336 | } 337 | 338 | fieldValue = reflect.Indirect(reflect.ValueOf(bean)).FieldByName(fields[0]) 339 | fieldValue = fieldValue.FieldByName(fields[1]) 340 | } else { 341 | fieldValue = reflect.Indirect(reflect.ValueOf(bean)).FieldByName(col.FieldName) 342 | } 343 | return fieldValue 344 | } 345 | 346 | // database table 347 | type Table struct { 348 | Name string 349 | Type reflect.Type 350 | ColumnsSeq []string 351 | Columns map[string]*Column 352 | Indexes map[string]*Index 353 | PrimaryKeys []string 354 | AutoIncrement string 355 | Created map[string]bool 356 | Updated string 357 | Version string 358 | Cacher Cacher 359 | } 360 | 361 | /* 362 | func NewTable(name string, t reflect.Type) *Table { 363 | return &Table{Name: name, Type: t, 364 | ColumnsSeq: make([]string, 0), 365 | Columns: make(map[string]*Column), 366 | Indexes: make(map[string]*Index), 367 | Created: make(map[string]bool), 368 | } 369 | }*/ 370 | 371 | // if has primary key, return column 372 | func (table *Table) PKColumns() []*Column { 373 | columns := make([]*Column, 0) 374 | for _, name := range table.PrimaryKeys { 375 | columns = append(columns, table.Columns[strings.ToLower(name)]) 376 | } 377 | return columns 378 | } 379 | 380 | func (table *Table) AutoIncrColumn() *Column { 381 | return table.Columns[strings.ToLower(table.AutoIncrement)] 382 | } 383 | 384 | func (table *Table) VersionColumn() *Column { 385 | return table.Columns[strings.ToLower(table.Version)] 386 | } 387 | 388 | // add a column to table 389 | func (table *Table) AddColumn(col *Column) { 390 | table.ColumnsSeq = append(table.ColumnsSeq, col.Name) 391 | table.Columns[strings.ToLower(col.Name)] = col 392 | if col.IsPrimaryKey { 393 | table.PrimaryKeys = append(table.PrimaryKeys, col.Name) 394 | } 395 | if col.IsAutoIncrement { 396 | table.AutoIncrement = col.Name 397 | } 398 | if col.IsCreated { 399 | table.Created[col.Name] = true 400 | } 401 | if col.IsUpdated { 402 | table.Updated = col.Name 403 | } 404 | if col.IsVersion { 405 | table.Version = col.Name 406 | } 407 | } 408 | 409 | // add an index or an unique to table 410 | func (table *Table) AddIndex(index *Index) { 411 | table.Indexes[index.Name] = index 412 | } 413 | 414 | func (table *Table) genCols(session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) { 415 | colNames := make([]string, 0) 416 | args := make([]interface{}, 0) 417 | 418 | for _, col := range table.Columns { 419 | lColName := strings.ToLower(col.Name) 420 | if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { 421 | if _, ok := session.Statement.columnMap[lColName]; !ok { 422 | continue 423 | } 424 | } 425 | if col.MapType == ONLYFROMDB { 426 | continue 427 | } 428 | 429 | fieldValue := col.ValueOf(bean) 430 | if col.IsAutoIncrement { 431 | switch fieldValue.Type().Kind() { 432 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: 433 | if fieldValue.Int() == 0 { 434 | continue 435 | } 436 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: 437 | if fieldValue.Uint() == 0 { 438 | continue 439 | } 440 | case reflect.String: 441 | if len(fieldValue.String()) == 0 { 442 | continue 443 | } 444 | } 445 | } 446 | 447 | if session.Statement.ColumnStr != "" { 448 | if _, ok := session.Statement.columnMap[lColName]; !ok { 449 | continue 450 | } 451 | } 452 | if session.Statement.OmitStr != "" { 453 | if _, ok := session.Statement.columnMap[lColName]; ok { 454 | continue 455 | } 456 | } 457 | 458 | if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { 459 | args = append(args, time.Now()) 460 | } else if col.IsVersion && session.Statement.checkVersion { 461 | args = append(args, 1) 462 | } else { 463 | arg, err := session.value2Interface(col, fieldValue) 464 | if err != nil { 465 | return colNames, args, err 466 | } 467 | args = append(args, arg) 468 | } 469 | 470 | if includeQuote { 471 | colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?") 472 | } else { 473 | colNames = append(colNames, col.Name) 474 | } 475 | } 476 | return colNames, args, nil 477 | } 478 | 479 | // Conversion is an interface. A type implements Conversion will according 480 | // the custom method to fill into database and retrieve from database. 481 | type Conversion interface { 482 | FromDB([]byte) error 483 | ToDB() ([]byte, error) 484 | } 485 | -------------------------------------------------------------------------------- /docs/QuickStartEn.md: -------------------------------------------------------------------------------- 1 | Quick Start 2 | ===== 3 | 4 | * [1.Create ORM Engine](#10) 5 | * [2.Define a struct](#20) 6 | * [2.1.Name mapping rule](#21) 7 | * [2.2.Use Table or Tag to change table or column name](#22) 8 | * [2.3.Column define](#23) 9 | * [3. database schema operation](#30) 10 | * [3.1.Retrieve database schema infomation](#31) 11 | * [3.2.Table Operation](#32) 12 | * [3.3.Create indexes and uniques](#33) 13 | * [3.4.Sync database schema](#34) 14 | * [4.Insert records](#40) 15 | * [5.Query and Count records](#60) 16 | * [5.1.Query condition methods](#61) 17 | * [5.2.Temporory methods](#62) 18 | * [5.3.Get](#63) 19 | * [5.4.Find](#64) 20 | * [5.5.Iterate](#65) 21 | * [5.6.Count](#66) 22 | * [6.Update records](#70) 23 | * [6.1.Optimistic Locking](#71) 24 | * [7.Delete records](#80) 25 | * [8.Execute SQL command](#90) 26 | * [9.Execute SQL query](#100) 27 | * [10.Transaction](#110) 28 | * [11.Cache](#120) 29 | * [12.Xorm Tool](#130) 30 | * [12.1.Reverse command](#131) 31 | * [13.Examples](#140) 32 | * [14.Cases](#150) 33 | * [15.FAQ](#160) 34 | * [16.Discuss](#170) 35 | 36 | 37 | ## 1.Create ORM Engine 38 | 39 | When using xorm, you can create multiple orm engines, an engine means a databse. So you can: 40 | 41 | ```Go 42 | import ( 43 | _ "github.com/go-sql-driver/mysql" 44 | "github.com/lunny/xorm" 45 | ) 46 | engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8") 47 | defer engine.Close() 48 | ``` 49 | 50 | or 51 | 52 | ```Go 53 | import ( 54 | _ "github.com/mattn/go-sqlite3" 55 | "github.com/lunny/xorm" 56 | ) 57 | engine, err = xorm.NewEngine("sqlite3", "./test.db") 58 | defer engine.Close() 59 | ``` 60 | 61 | Generally, you can only create one engine. Engine supports run on go rutines. 62 | 63 | xorm supports four drivers now: 64 | 65 | * Mysql: [github.com/Go-SQL-Driver/MySQL](https://github.com/Go-SQL-Driver/MySQL) 66 | 67 | * MyMysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql/godrv) 68 | 69 | * SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) 70 | 71 | * Postgres: [github.com/lib/pq](https://github.com/lib/pq) 72 | 73 | NewEngine's parameters are the same as `sql.Open`. So you should read the drivers' document for parameters' usage. 74 | 75 | After engine created, you can do some settings. 76 | 77 | 1.Logs 78 | 79 | * `engine.ShowSQL = true`, Show SQL statement on standard output; 80 | * `engine.ShowDebug = true`, Show debug infomation on standard output; 81 | * `engine.ShowError = true`, Show error infomation on standard output; 82 | * `engine.ShowWarn = true`, Show warnning information on standard output; 83 | 84 | 2.If want to record infomation with another method: use `engine.Logger` as `io.Writer`: 85 | 86 | ```Go 87 | f, err := os.Create("sql.log") 88 | if err != nil { 89 | println(err.Error()) 90 | return 91 | } 92 | engine.Logger = f 93 | ``` 94 | 95 | 3.Engine support connection pool. The default pool is database/sql's and also you can use custom pool. Xorm provides two connection pool `xorm.NonConnectionPool` & `xorm.SimpleConnectPool`. If you want to use yourself pool, you can use `engine.SetPool` to set it. 96 | 97 | * Use `engine.SetIdleConns()` to set idle connections. 98 | * Use `engine.SetMaxConns()` to set Max connections. This methods support only Go 1.2+. 99 | 100 | 101 | ## 2.Define struct 102 | 103 | xorm map a struct to a database table, the rule is below. 104 | 105 | 106 | ### 2.1.name mapping rule 107 | 108 | use xorm.IMapper interface to implement. There are two IMapper implemented: `SnakeMapper` and `SameMapper`. SnakeMapper means struct name is word by word and table name or column name as 下划线. SameMapper means same name between struct and table. 109 | 110 | SnakeMapper is the default. 111 | 112 | ```Go 113 | engine.Mapper = SameMapper{} 114 | ``` 115 | 116 | 同时需要注意的是: 117 | 118 | * 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 119 | * 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: 120 | 121 | ```Go 122 | engine.SetTableMapper(SameMapper{}) 123 | engine.SetColumnMapper(SnakeMapper{}) 124 | ``` 125 | 126 | 127 | ### 2.2.前缀映射规则,后缀映射规则和缓存映射规则 128 | 129 | * 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 130 | * 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 131 | * 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。 132 | 133 | 当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 134 | 135 | 136 | ### 2.3.使用Table和Tag改变名称映射 137 | 138 | 如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 139 | 140 | 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 141 | 142 | 143 | ### 2.4.Column属性定义 144 | 我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如: 145 | 146 | ``` 147 | type User struct { 148 | Id int64 149 | Name string `xorm:"varchar(25) not null unique 'usr_name'"` 150 | } 151 | ``` 152 | 153 | 对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。 154 | 155 | 具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写: 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名
pk是否是Primary Key,当前仅支持int64类型
当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)字段类型
autoincr是否是自增
[not ]null是否可以为空
unique或unique(uniquename)是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引
index或index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引
extends应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中
-这个Field将不进行字段映射
->这个Field将只写入到数据库而不从数据库读取
<-这个Field将只从数据库读取,而不写入到数据库
createdThis field will be filled in current time on insert
updatedThis field will be filled in current time on insert or update
versionThis field will be filled 1 on insert and autoincrement on update
default 0设置默认值,紧跟的内容如果是Varchar等需要加上单引号
204 | 205 | 另外有如下几条自动映射的规则: 206 | 207 | - 1.如果field名称为`Id`而且类型为`int64`的话,会被xorm视为主键,并且拥有自增属性。如果想用`Id`以外的名字做为主键名,可以在对应的Tag上加上`xorm:"pk"`来定义主键。 208 | 209 | - 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义 210 | 211 | - 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。 212 | 213 | - 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。 214 | ```Go 215 | type Conversion interface { 216 | FromDB([]byte) error 217 | ToDB() ([]byte, error) 218 | } 219 | ``` 220 | 221 | 222 | ## 3.表结构操作 223 | 224 | xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。 225 | 226 | 227 | ## 3.1 获取数据库信息 228 | 229 | * DBMetas() 230 | xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表的信息 231 | 232 | 233 | ## 3.2.表操作 234 | 235 | * CreateTables() 236 | 创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。 237 | 238 | * IsTableEmpty() 239 | 判断表是否为空,参数和CreateTables相同 240 | 241 | * IsTableExist() 242 | 判断表是否存在 243 | 244 | * DropTables() 245 | 删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。 246 | 247 | 248 | ## 3.3.创建索引和唯一索引 249 | 250 | * CreateIndexes 251 | 根据struct中的tag来创建索引 252 | 253 | * CreateUniques 254 | 根据struct中的tag来创建唯一索引 255 | 256 | 257 | ## 3.4.同步数据库结构 258 | 259 | 同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现: 260 | 1) 自动检测和创建表,这个检测是根据表的名字 261 | 2)自动检测和新增表中的字段,这个检测是根据字段名 262 | 3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 263 | 264 | 调用方法如下: 265 | ```Go 266 | err := engine.Sync(new(User)) 267 | ``` 268 | 269 | 270 | ## 4.插入数据 271 | 272 | Inserting records use Insert method. 273 | 274 | * Insert one record 275 | ```Go 276 | user := new(User) 277 | user.Name = "myname" 278 | affected, err := engine.Insert(user) 279 | ``` 280 | 281 | After inseted, `user.Id` will be filled with primary key column value. 282 | ```Go 283 | fmt.Println(user.Id) 284 | ``` 285 | 286 | * Insert multiple records by Slice on one table 287 | ```Go 288 | users := make([]User, 0) 289 | users[0].Name = "name0" 290 | ... 291 | affected, err := engine.Insert(&users) 292 | ``` 293 | 294 | * Insert multiple records by Slice of pointer on one table 295 | ```Go 296 | users := make([]*User, 0) 297 | users[0] = new(User) 298 | users[0].Name = "name0" 299 | ... 300 | affected, err := engine.Insert(&users) 301 | ``` 302 | 303 | * Insert one record on two table. 304 | ```Go 305 | user := new(User) 306 | user.Name = "myname" 307 | question := new(Question) 308 | question.Content = "whywhywhwy?" 309 | affected, err := engine.Insert(user, question) 310 | ``` 311 | 312 | * Insert multiple records on multiple tables. 313 | ```Go 314 | users := make([]User, 0) 315 | users[0].Name = "name0" 316 | ... 317 | questions := make([]Question, 0) 318 | questions[0].Content = "whywhywhwy?" 319 | affected, err := engine.Insert(&users, &questions) 320 | ``` 321 | 322 | * Insert one or multple records on multiple tables. 323 | ```Go 324 | user := new(User) 325 | user.Name = "myname" 326 | ... 327 | questions := make([]Question, 0) 328 | questions[0].Content = "whywhywhwy?" 329 | affected, err := engine.Insert(user, &questions) 330 | ``` 331 | 332 | Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert. 333 | 334 | 335 | ## 5.Query and count 336 | 337 | 所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。 338 | 339 | 340 | ### 5.1.查询条件方法 341 | 342 | 查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: 343 | 344 | * Id(int64) 345 | 传入一个PK字段的值,作为查询条件 346 | 347 | * Where(string, …interface{}) 348 | 和Where语句中的条件基本相同,作为条件 349 | 350 | * And(string, …interface{}) 351 | 和Where函数中的条件基本相同,作为条件 352 | 353 | * Or(string, …interface{}) 354 | 和Where函数中的条件基本相同,作为条件 355 | 356 | * Sql(string, …interface{}) 357 | 执行指定的Sql语句,并把结果映射到结构体 358 | 359 | * Asc(…string) 360 | 指定字段名正序排序 361 | 362 | * Desc(…string) 363 | 指定字段名逆序排序 364 | 365 | * OrderBy(string) 366 | 按照指定的顺序进行排序 367 | 368 | * In(string, …interface{}) 369 | 某字段在一些值中 370 | 371 | * Cols(…string) 372 | 只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: 373 | ```Go 374 | engine.Cols("age", "name").Find(&users) 375 | // SELECT age, name FROM user 376 | engine.Cols("age", "name").Update(&user) 377 | // UPDATE user SET age=? AND name=? 378 | ``` 379 | 380 | 其中的参数"age", "name"也可以写成"age, name",两种写法均可 381 | 382 | * Omit(...string) 383 | 和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 384 | ```Go 385 | engine.Cols("age").Update(&user) 386 | // UPDATE user SET name = ? AND department = ? 387 | ``` 388 | 389 | * Distinct(…string) 390 | 按照参数中指定的字段归类结果 391 | ```Go 392 | engine.Distinct("age", "department").Find(&users) 393 | // SELECT DISTINCT age, department FROM user 394 | ``` 395 | 注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id 396 | 397 | * Table(nameOrStructPtr interface{}) 398 | 传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名 399 | 400 | * Limit(int, …int) 401 | 限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置 402 | 403 | * Top(int) 404 | 相当于Limit(int, 0) 405 | 406 | * Join(string,string,string) 407 | 第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值,第二个参数为表名,第三个参数为连接条件 408 | 409 | * GroupBy(string) 410 | Groupby的参数字符串 411 | 412 | * Having(string) 413 | Having的参数字符串 414 | 415 | 416 | ### 5.2.临时开关方法 417 | 418 | * NoAutoTime() 419 | 如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间 420 | 421 | * NoCache() 422 | 如果此方法执行,则此次生成的语句则在非缓存模式下执行 423 | 424 | * UseBool(...string) 425 | 当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。 426 | 427 | * Cascade(bool) 428 | 是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。 429 | 430 | 431 | ### 5.3.Get one record 432 | Fetch a single object by user 433 | 434 | ```Go 435 | var user = User{Id:27} 436 | has, err := engine.Get(&user) 437 | // or has, err := engine.Id(27).Get(&user) 438 | 439 | var user = User{Name:"xlw"} 440 | has, err := engine.Get(&user) 441 | ``` 442 | 443 | 444 | ### 5.4.Find 445 | Fetch multipe objects into a slice or a map, use Find: 446 | 447 | ```Go 448 | var everyone []Userinfo 449 | err := engine.Find(&everyone) 450 | 451 | users := make(map[int64]Userinfo) 452 | err := engine.Find(&users) 453 | ``` 454 | 455 | * also you can use Where, Limit 456 | 457 | ```Go 458 | var allusers []Userinfo 459 | err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 460 | ``` 461 | 462 | * or you can use a struct query 463 | 464 | ```Go 465 | var tenusers []Userinfo 466 | err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0 467 | ``` 468 | 469 | * or In function 470 | 471 | ```Go 472 | var tenusers []Userinfo 473 | err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5) 474 | ``` 475 | 476 | * The default will query all columns of a table. Use Cols function if you want to select some columns 477 | 478 | ```Go 479 | var tenusers []Userinfo 480 | err := engine.Cols("id", "name").Find(&tenusers) //Find only id and name 481 | ``` 482 | 483 | 484 | ### 5.5.Iterate records 485 | Iterate, like find, but handle records one by one 486 | 487 | ```Go 488 | err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{ 489 | user := bean.(*Userinfo) 490 | //do somthing use i and user 491 | }) 492 | ``` 493 | 494 | 495 | ### 5.6.Count方法 496 | 497 | 统计数据使用`Count`方法,Count方法的参数为struct的指针并且成为查询条件。 498 | ```Go 499 | user := new(User) 500 | total, err := engine.Where("id >?", 1).Count(user) 501 | ``` 502 | 503 | 504 | ## 6.更新数据 505 | 506 | 更新数据使用`Update`方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。 507 | 508 | ```Go 509 | user := new(User) 510 | user.Name = "myname" 511 | affected, err := engine.Id(id).Update(user) 512 | ``` 513 | 514 | 这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择: 515 | 516 | 1. 通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。 517 | ```Go 518 | affected, err := engine.Id(id).Cols("age").Update(&user) 519 | ``` 520 | 521 | 2. 通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。 522 | ```Go 523 | affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"age":0}) 524 | ``` 525 | 526 | 527 | ### 6.1.乐观锁 528 | 529 | 要使用乐观锁,需要使用version标记 530 | type User struct { 531 | Id int64 532 | Name string 533 | Version int `xorm:"version"` 534 | } 535 | 536 | 在Insert时,version标记的字段将会被设置为1,在Update时,Update的内容必须包含version原来的值。 537 | 538 | ```Go 539 | var user User 540 | engine.Id(1).Get(&user) 541 | // SELECT * FROM user WHERE id = ? 542 | engine.Id(1).Update(&user) 543 | // UPDATE user SET ..., version = version + 1 WHERE id = ? AND version = ? 544 | ``` 545 | 546 | 547 | 548 | ## 7.Delete one or more records 549 | Delete one or more records 550 | 551 | * delete by id 552 | 553 | ```Go 554 | err := engine.Id(1).Delete(&User{}) 555 | ``` 556 | 557 | * delete by other conditions 558 | 559 | ```Go 560 | err := engine.Delete(&User{Name:"xlw"}) 561 | ``` 562 | 563 | 564 | ## 8.Execute SQL query 565 | 566 | Of course, SQL execution is also provided. 567 | 568 | If select then use Query 569 | 570 | ```Go 571 | sql := "select * from userinfo" 572 | results, err := engine.Query(sql) 573 | ``` 574 | 575 | 576 | ## 9.Execute SQL command 577 | If insert, update or delete then use Exec 578 | 579 | ```Go 580 | sql = "update userinfo set username=? where id=?" 581 | res, err := engine.Exec(sql, "xiaolun", 1) 582 | ``` 583 | 584 | 585 | ## 10.Transaction 586 | 587 | ```Go 588 | session := engine.NewSession() 589 | defer session.Close() 590 | 591 | // add Begin() before any action 592 | err := session.Begin() 593 | user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} 594 | _, err = session.Insert(&user1) 595 | if err != nil { 596 | session.Rollback() 597 | return 598 | } 599 | user2 := Userinfo{Username: "yyy"} 600 | _, err = session.Where("id = ?", 2).Update(&user2) 601 | if err != nil { 602 | session.Rollback() 603 | return 604 | } 605 | 606 | _, err = session.Exec("delete from userinfo where username = ?", user2.Username) 607 | if err != nil { 608 | session.Rollback() 609 | return 610 | } 611 | 612 | // add Commit() after all actions 613 | err = session.Commit() 614 | if err != nil { 615 | return 616 | } 617 | ``` 618 | 619 | 620 | ## 11.缓存 621 | 622 | 1. Global Cache 623 | Xorm implements cache support. Defaultly, it's disabled. If enable it, use below code. 624 | 625 | ```Go 626 | cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) 627 | engine.SetDefaultCacher(cacher) 628 | ``` 629 | 630 | If disable some tables' cache, then: 631 | 632 | ```Go 633 | engine.MapCacher(&user, nil) 634 | ``` 635 | 636 | 2. Table's Cache 637 | If only some tables need cache, then: 638 | 639 | ```Go 640 | cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) 641 | engine.MapCacher(&user, cacher) 642 | ``` 643 | 644 | Caution: 645 | 646 | 1. When use Cols methods on cache enabled, the system still return all the columns. 647 | 648 | 2. When using Exec method, you should clear cache: 649 | 650 | ```Go 651 | engine.Exec("update user set name = ? where id = ?", "xlw", 1) 652 | engine.ClearCache(new(User)) 653 | ``` 654 | 655 | Cache implement theory below: 656 | 657 | ![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png) 658 | 659 | 660 | ## 12.xorm tool 661 | xorm工具提供了xorm命令,能够帮助做很多事情。 662 | 663 | ### 12.1.Reverse command 664 | Please visit [xorm tool](https://github.com/lunny/xorm/tree/master/xorm) 665 | 666 | 667 | ## 13.Examples 668 | 669 | 请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples) 670 | 671 | 672 | ## 14.Cases 673 | 674 | * [Gowalker](http://gowalker.org),source [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) 675 | 676 | * [GoDaily](http://godaily.org),source [github.com/govc/godaily](http://github.com/govc/godaily) 677 | 678 | * [Sudochina](http://sudochina.com) source [github.com/insionng/toropress](http://github.com/insionng/toropress) 679 | 680 | * [VeryHour](http://veryhour.com) 681 | 682 | 683 | ## 15.FAQ 684 | 685 | 1.How the xorm tag use both with json? 686 | 687 | Use space. 688 | 689 | ```Go 690 | type User struct { 691 | Name string `json:"name" xorm:"name"` 692 | } 693 | ``` 694 | --------------------------------------------------------------------------------