├── 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 | | go type's kind
6 | |
7 | value method |
8 | xorm type
9 | |
10 |
11 |
12 | | implemented Conversion |
13 | Conversion.ToDB / Conversion.FromDB |
14 | Text |
15 |
16 |
17 | | int, int8, int16, int32, uint, uint8, uint16, uint32 |
18 | |
19 | Int |
20 |
21 |
22 | | int64, uint64 | | BigInt |
23 |
24 | | float32 | | Float |
25 |
26 | | float64 | | Double |
27 |
28 | | complex64, complex128 |
29 | json.Marshal / json.UnMarshal |
30 | Varchar(64) |
31 |
32 |
33 | | []uint8 | | Blob |
34 |
35 |
36 | | array, slice, map except []uint8 |
37 | json.Marshal / json.UnMarshal |
38 | Text |
39 |
40 |
41 | | bool | 1 or 0 | Bool |
42 |
43 |
44 | | string | | Varchar(255) |
45 |
46 |
47 | | time.Time | | DateTime |
48 |
49 |
50 | | cascade struct | primary key field value | BigInt |
51 |
52 |
53 |
54 | | struct | json.Marshal / json.UnMarshal | Text |
55 |
56 |
57 | |
58 | Others
59 | |
60 | |
61 |
62 | Text
63 | |
64 |
65 |
--------------------------------------------------------------------------------
/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 | [](https://drone.io/github.com/lunny/xorm/latest) [](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 | | xorm
4 | |
5 | mysql
6 | |
7 | sqlite3
8 | |
9 | postgres
10 | |
11 | remark |
12 |
13 |
14 |
15 | | BIT
16 | |
17 | BIT
18 | |
19 | INTEGER
20 | |
21 | BIT
22 | |
23 | |
24 |
25 |
26 |
27 | | TINYINT
28 | |
29 | TINYINT
30 | |
31 | INTEGER
32 | |
33 | SMALLINT
34 | |
35 | |
36 |
37 |
38 |
39 |
40 | | SMALLINT
41 | |
42 | SMALLINT
43 | |
44 | INTEGER
45 | |
46 | SMALLINT
47 | |
48 | |
49 |
50 |
51 |
52 |
53 | | MEDIUMINT
54 | |
55 | MEDIUMINT
56 | |
57 | INTEGER
58 | |
59 | INTEGER
60 | |
61 | |
62 |
63 |
64 |
65 |
66 | | INT
67 | |
68 | INT
69 | |
70 | INTEGER
71 | |
72 | INTEGER
73 | |
74 | |
75 |
76 |
77 |
78 | | INTEGER
79 | |
80 | INTEGER
81 | |
82 | INTEGER
83 | |
84 | INTEGER
85 | |
86 | |
87 |
88 |
89 |
90 |
91 | | BIGINT
92 | |
93 | BIGINT
94 | |
95 | INTEGER
96 | |
97 | BIGINT
98 | |
99 | |
100 |
101 | |
102 |
103 |
104 | | CHAR
105 | |
106 | CHAR
107 | |
108 | TEXT
109 | |
110 | CHAR
111 | |
112 | |
113 |
114 |
115 |
116 |
117 | | VARCHAR
118 | |
119 | VARCHAR
120 | |
121 | TEXT
122 | |
123 | VARCHAR
124 | |
125 | |
126 |
127 |
128 |
129 |
130 | | TINYTEXT
131 | |
132 | TINYTEXT
133 | |
134 | TEXT
135 | |
136 | TEXT
137 | |
138 | |
139 |
140 |
141 |
142 | | TEXT
143 | |
144 | TEXT
145 | |
146 | TEXT
147 | |
148 | TEXT
149 | |
150 | |
151 |
152 |
153 |
154 | | MEDIUMTEXT
155 | |
156 | MEDIUMTEXT
157 | |
158 | TEXT
159 | |
160 | TEXT
161 | |
162 | |
163 |
164 |
165 |
166 |
167 | | LONGTEXT
168 | |
169 | LONGTEXT
170 | |
171 | TEXT
172 | |
173 | TEXT
174 | |
175 | |
176 |
177 | |
178 |
179 |
180 | | BINARY
181 | |
182 | BINARY
183 | |
184 | BLOB
185 | |
186 | BYTEA
187 | |
188 | |
189 |
190 |
191 |
192 |
193 | | VARBINARY
194 | |
195 | VARBINARY
196 | |
197 | BLOB
198 | |
199 | BYTEA
200 | |
201 | |
202 |
203 | |
204 |
205 |
206 | | DATE
207 | |
208 | DATE
209 | |
210 | NUMERIC
211 | |
212 | DATE
213 | |
214 | |
215 |
216 |
217 |
218 |
219 | | DATETIME
220 | |
221 | DATETIME
222 | |
223 | NUMERIC
224 | |
225 | TIMESTAMP
226 | |
227 | |
228 |
229 |
230 |
231 |
232 | | TIME
233 | |
234 | TIME
235 | |
236 | NUMERIC
237 | |
238 | TIME
239 | |
240 | |
241 |
242 |
243 |
244 |
245 | | TIMESTAMP
246 | |
247 | TIMESTAMP
248 | |
249 | NUMERIC
250 | |
251 | TIMESTAMP
252 | |
253 | |
254 |
255 |
256 |
257 |
258 | | TIMESTAMPZ
259 | |
260 | TEXT
261 | |
262 | TEXT
263 | |
264 | TIMESTAMP with zone
265 | |
266 | timestamp with zone info |
267 |
268 |
269 | |
270 |
271 | | REAL
272 | |
273 | REAL
274 | |
275 | REAL
276 | |
277 | REAL
278 | |
279 | |
280 |
281 |
282 |
283 |
284 | | FLOAT
285 | |
286 | FLOAT
287 | |
288 | REAL
289 | |
290 | REAL
291 | |
292 | |
293 |
294 |
295 |
296 |
297 | | DOUBLE
298 | |
299 | DOUBLE
300 | |
301 | REAL
302 | |
303 | DOUBLE PRECISION
304 | |
305 | |
306 |
307 |
308 | |
309 |
310 | | DECIMAL
311 | |
312 | DECIMAL
313 | |
314 | NUMERIC
315 | |
316 | DECIMAL
317 | |
318 | |
319 |
320 |
321 |
322 |
323 | | NUMERIC
324 | |
325 | NUMERIC
326 | |
327 | NUMERIC
328 | |
329 | NUMERIC
330 | |
331 | |
332 |
333 |
334 | |
335 |
336 | | TINYBLOB
337 | |
338 | TINYBLOB
339 | |
340 | BLOB
341 | |
342 | BYTEA
343 | |
344 | |
345 |
346 |
347 |
348 |
349 | | BLOB
350 | |
351 | BLOB
352 | |
353 | BLOB
354 | |
355 | BYTEA
356 | |
357 | |
358 |
359 |
360 |
361 |
362 | | MEDIUMBLOB
363 | |
364 | MEDIUMBLOB
365 | |
366 | BLOB
367 | |
368 | BYTEA
369 | |
370 | |
371 |
372 |
373 |
374 |
375 | | LONGBLOB
376 | |
377 | LONGBLOB
378 | |
379 | BLOB
380 | |
381 | BYTEA
382 | |
383 | |
384 |
385 |
386 |
387 | | BYTEA
388 | |
389 | BLOB
390 | |
391 | BLOB
392 | |
393 | BYTEA
394 | |
395 | |
396 |
397 |
398 | |
399 |
400 |
401 | | BOOL
402 | |
403 | TINYINT
404 | |
405 | INTEGER
406 | |
407 | BOOLEAN
408 | |
409 | |
410 |
411 |
412 |
413 |
414 | | SERIAL
415 | |
416 | INT
417 | |
418 | INTEGER
419 | |
420 | SERIAL
421 | |
422 | auto increment |
423 |
424 |
425 |
426 | | BIGSERIAL
427 | |
428 | BIGINT
429 | |
430 | INTEGER
431 | |
432 | BIGSERIAL
433 | |
434 | auto increment |
435 |
436 |
437 |
438 |
--------------------------------------------------------------------------------
/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 | | name | 当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名 |
160 |
161 |
162 | | pk | 是否是Primary Key,当前仅支持int64类型 |
163 |
164 |
165 | | 当前支持30多种字段类型,详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md) | 字段类型 |
166 |
167 |
168 | | autoincr | 是否是自增 |
169 |
170 |
171 | | [not ]null | 是否可以为空 |
172 |
173 |
174 | | unique或unique(uniquename) | 是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引 |
175 |
176 |
177 | | index或index(indexname) | 是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引 |
178 |
179 |
180 | | extends | 应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中 |
181 |
182 |
183 | | - | 这个Field将不进行字段映射 |
184 |
185 |
186 | | -> | 这个Field将只写入到数据库而不从数据库读取 |
187 |
188 |
189 | | <- | 这个Field将只从数据库读取,而不写入到数据库 |
190 |
191 |
192 | | created | This field will be filled in current time on insert |
193 |
194 |
195 | | updated | This field will be filled in current time on insert or update |
196 |
197 |
198 | | version | This field will be filled 1 on insert and autoincrement on update |
199 |
200 |
201 | | default 0 | 设置默认值,紧跟的内容如果是Varchar等需要加上单引号 |
202 |
203 |
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 | 
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 |
--------------------------------------------------------------------------------