Compare commits
27 Commits
Author | SHA1 | Date |
---|---|---|
dtapps | 5f70e83104 | 1 month ago |
dtapps | 8dbbff4a27 | 2 months ago |
dtapps | 0ed080ee44 | 2 months ago |
dtapps | b99cbca10a | 2 months ago |
dtapps | 8238616ab5 | 2 months ago |
dtapps | b4cf941dd9 | 2 months ago |
dtapps | 1f3831a39c | 2 months ago |
dtapps | bb1ce11d98 | 4 months ago |
dtapps | 0799a47c4d | 4 months ago |
dtapps | 986c3d81aa | 6 months ago |
李光春 | ce71070f19 | 2 years ago |
李光春 | 2480a2f0fb | 2 years ago |
李光春 | 7bd2d5fea4 | 2 years ago |
李光春 | 002b617947 | 2 years ago |
李光春 | 093c682a11 | 2 years ago |
李光春 | a92d63c3fa | 2 years ago |
李光春 | fbb0a0ef38 | 2 years ago |
李光春 | cda216ae0e | 2 years ago |
李光春 | 5cf7e80176 | 2 years ago |
李光春 | 0b0d3b35ad | 2 years ago |
李光春 | 3b662a2351 | 2 years ago |
李光春 | a383dc98c2 | 2 years ago |
李光春 | dbcb880465 | 2 years ago |
李光春 | 284e13594a | 2 years ago |
李光春 | f1530064e9 | 2 years ago |
李光春 | 54ca833aa5 | 2 years ago |
李光春 | a184e64f15 | 2 years ago |
@ -1,9 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/beego/beego/v2/client/orm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BeegoClient struct {
|
|
||||||
Db orm.Ormer // 驱动
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/beego/beego/v2/client/orm"
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewBeegoMysqlClient(dns string) (*BeegoClient, error) {
|
|
||||||
|
|
||||||
c := &BeegoClient{}
|
|
||||||
|
|
||||||
err := orm.RegisterDataBase("default", "mysql", dns)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Db = orm.NewOrm()
|
|
||||||
|
|
||||||
return c, err
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/beego/beego/v2/client/orm"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewBeegoPostgresClient(dns string) (*BeegoClient, error) {
|
|
||||||
|
|
||||||
c := &BeegoClient{}
|
|
||||||
|
|
||||||
err := orm.RegisterDataBase("default", "postgres", dns)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Db = orm.NewOrm()
|
|
||||||
|
|
||||||
return c, err
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigBunClient struct {
|
|
||||||
Dns string // 地址
|
|
||||||
}
|
|
||||||
|
|
||||||
// BunClient
|
|
||||||
// https://bun.uptrace.dev/
|
|
||||||
type BunClient struct {
|
|
||||||
Db *bun.DB // 驱动
|
|
||||||
config *ConfigBunClient // 配置
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetDb 获取驱动
|
|
||||||
func (c *BunClient) GetDb() *bun.DB {
|
|
||||||
return c.Db
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
"github.com/uptrace/bun/dialect/mysqldialect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewBunMysqlClient(config *ConfigBunClient) (*BunClient, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c := &BunClient{config: config}
|
|
||||||
|
|
||||||
sqlDb, err := sql.Open("mysql", c.config.Dns)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Db = bun.NewDB(sqlDb, mysqldialect.New())
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
"github.com/uptrace/bun/dialect/pgdialect"
|
|
||||||
"github.com/uptrace/bun/driver/pgdriver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewBunPgsqlClient(config *ConfigBunClient) (*BunClient, error) {
|
|
||||||
|
|
||||||
c := &BunClient{config: config}
|
|
||||||
|
|
||||||
sqlDb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(c.config.Dns)))
|
|
||||||
|
|
||||||
c.Db = bun.NewDB(sqlDb, pgdialect.New())
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
@ -1,87 +1,57 @@
|
|||||||
module go.dtapp.net/dorm
|
module go.dtapp.net/dorm
|
||||||
|
|
||||||
go 1.19
|
go 1.22.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/basgys/goxml2json v1.1.0
|
github.com/redis/go-redis/v9 v9.5.1
|
||||||
github.com/beego/beego/v2 v2.0.5
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/go-redis/redis/v9 v9.0.0-beta.2
|
go.dtapp.net/gojson v1.0.4
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
go.dtapp.net/gotime v1.0.8
|
||||||
github.com/kamva/mgm/v3 v3.4.1
|
go.mongodb.org/mongo-driver v1.15.0
|
||||||
github.com/lib/pq v1.10.7
|
gorm.io/driver/mysql v1.5.6
|
||||||
github.com/qiniu/qmgo v1.1.2
|
gorm.io/driver/postgres v1.5.7
|
||||||
github.com/rs/xid v1.4.0
|
gorm.io/gen v0.3.26
|
||||||
github.com/sirupsen/logrus v1.9.0
|
gorm.io/gorm v1.25.10
|
||||||
github.com/upper/db/v4 v4.6.0
|
|
||||||
github.com/uptrace/bun v1.1.8
|
|
||||||
github.com/uptrace/bun/dialect/mysqldialect v1.1.8
|
|
||||||
github.com/uptrace/bun/dialect/pgdialect v1.1.8
|
|
||||||
github.com/uptrace/bun/driver/pgdriver v1.1.8
|
|
||||||
go.dtapp.net/gotime v1.0.5
|
|
||||||
go.dtapp.net/goxml v1.0.1
|
|
||||||
go.mongodb.org/mongo-driver v1.10.2
|
|
||||||
gorm.io/driver/mysql v1.3.6
|
|
||||||
gorm.io/driver/postgres v1.3.9
|
|
||||||
gorm.io/gorm v1.23.9
|
|
||||||
xorm.io/xorm v1.3.2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/basgys/goxml2json v1.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
|
||||||
github.com/goccy/go-json v0.9.11 // indirect
|
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
|
||||||
github.com/jackc/pgconn v1.13.0 // indirect
|
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
github.com/jackc/pgtype v1.12.0 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.15.10 // indirect
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.6.6 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/segmentio/fasthash v1.0.3 // indirect
|
|
||||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
|
||||||
github.com/stretchr/testify v1.8.0 // indirect
|
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
|
||||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.1 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
|
golang.org/x/net v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/tools v0.1.11 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
golang.org/x/tools v0.20.0 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
gorm.io/datatypes v1.2.0 // indirect
|
||||||
mellium.im/sasl v0.3.0 // indirect
|
gorm.io/hints v1.1.2 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.7 // indirect
|
gorm.io/plugin/dbresolver v1.5.1 // indirect
|
||||||
modernc.org/libc v1.16.14 // indirect
|
|
||||||
modernc.org/opt v0.1.3 // indirect
|
|
||||||
modernc.org/sqlite v1.17.3 // indirect
|
|
||||||
modernc.org/strutil v1.1.2 // indirect
|
|
||||||
xorm.io/builder v0.3.12 // indirect
|
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import "database/sql"
|
||||||
|
|
||||||
|
// Ping ping
|
||||||
|
func (c *GormClient) Ping() error {
|
||||||
|
return c.sqlDd.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭
|
||||||
|
func (c *GormClient) Close() error {
|
||||||
|
return c.sqlDd.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats 返回数据库统计信息
|
||||||
|
func (c *GormClient) Stats() sql.DBStats {
|
||||||
|
return c.sqlDd.Stats()
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GormGenClientFun *GormClient 驱动
|
||||||
|
type GormGenClientFun func() *GormGenClient
|
||||||
|
|
||||||
|
// GormGenClientTableFun
|
||||||
|
// *GormClient 驱动
|
||||||
|
// string 表名
|
||||||
|
type GormGenClientTableFun func() (*GormGenClient, string)
|
||||||
|
|
||||||
|
// GormGenClientConfig 配置
|
||||||
|
type GormGenClientConfig struct {
|
||||||
|
Dns string // dns地址
|
||||||
|
Db *gorm.DB // db驱动
|
||||||
|
Config gen.Config // gen配置
|
||||||
|
}
|
||||||
|
|
||||||
|
// GormGenClient
|
||||||
|
// https://gorm.io/zh_CN/gen/index.html
|
||||||
|
type GormGenClient struct {
|
||||||
|
db *gorm.DB // 驱动
|
||||||
|
generator *gen.Generator // 驱动
|
||||||
|
config *GormGenClientConfig // 配置
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDb 获取驱动
|
||||||
|
func (c *GormGenClient) GetDb() *gorm.DB {
|
||||||
|
return c.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGenerator 获取驱动
|
||||||
|
func (c *GormGenClient) GetGenerator() *gen.Generator {
|
||||||
|
return c.generator
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGormGenMysqlClient 创建GormGenClient实例 mysql
|
||||||
|
func NewGormGenMysqlClient(ctx context.Context, config *GormGenClientConfig) (*GormGenClient, error) {
|
||||||
|
|
||||||
|
c := &GormGenClient{config: config}
|
||||||
|
|
||||||
|
c.generator = gen.NewGenerator(config.Config)
|
||||||
|
|
||||||
|
if c.config.Dns != "" {
|
||||||
|
c.db, _ = gorm.Open(mysql.Open(c.config.Dns), &gorm.Config{})
|
||||||
|
c.generator.UseDB(c.db)
|
||||||
|
} else {
|
||||||
|
c.generator.UseDB(c.config.Db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGormGenPostgresClient 创建GormGenClient实例 postgres
|
||||||
|
func NewGormGenPostgresClient(ctx context.Context, config *GormGenClientConfig) (*GormGenClient, error) {
|
||||||
|
|
||||||
|
c := &GormGenClient{config: config}
|
||||||
|
|
||||||
|
c.generator = gen.NewGenerator(config.Config)
|
||||||
|
|
||||||
|
if c.config.Dns != "" {
|
||||||
|
c.db, _ = gorm.Open(postgres.Open(c.config.Dns), &gorm.Config{})
|
||||||
|
c.generator.UseDB(c.db)
|
||||||
|
} else {
|
||||||
|
c.generator.UseDB(c.config.Db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGormGenPostgresqlClient 创建GormGenClient实例 postgresql
|
||||||
|
func NewGormGenPostgresqlClient(ctx context.Context, config *GormGenClientConfig) (*GormGenClient, error) {
|
||||||
|
|
||||||
|
c := &GormGenClient{config: config}
|
||||||
|
|
||||||
|
c.generator = gen.NewGenerator(config.Config)
|
||||||
|
|
||||||
|
if c.config.Dns != "" {
|
||||||
|
c.db, _ = gorm.Open(postgres.Open(c.config.Dns), &gorm.Config{})
|
||||||
|
c.generator.UseDB(c.db)
|
||||||
|
} else {
|
||||||
|
c.generator.UseDB(c.config.Db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
@ -1,8 +1,16 @@
|
|||||||
package dorm
|
package dorm
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
// GetDb 获取驱动
|
// GetDb 获取驱动
|
||||||
func (c *GormClient) GetDb() *gorm.DB {
|
func (c *GormClient) GetDb() *gorm.DB {
|
||||||
return c.Db
|
return c.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSqlDb 获取驱动
|
||||||
|
func (c *GormClient) GetSqlDb() *sql.DB {
|
||||||
|
return c.sqlDd
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
NoConfigDatabaseName = errors.New("没有配置库名")
|
||||||
|
NoConfigCollectionName = errors.New("没有配置集合名")
|
||||||
|
)
|
@ -0,0 +1,18 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
import "go.mongodb.org/mongo-driver/mongo"
|
||||||
|
|
||||||
|
// GetDb 获取驱动
|
||||||
|
func (cs *MongoSessionOptions) GetDb() *mongo.Client {
|
||||||
|
return cs.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSession 获取会话
|
||||||
|
func (cs *MongoSessionOptions) GetSession() mongo.Session {
|
||||||
|
return cs.session
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSessionContext 获取会话上下文
|
||||||
|
func (cs *MongoSessionOptions) GetSessionContext() mongo.SessionContext {
|
||||||
|
return cs.sessionContext
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package dorm
|
package dorm
|
||||||
|
|
||||||
import "github.com/go-redis/redis/v9"
|
import "github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
// GetDb 获取驱动
|
// GetDb 获取驱动
|
||||||
func (r *RedisClient) GetDb() *redis.Client {
|
func (r *RedisClient) GetDb() *redis.Client {
|
||||||
return r.Db
|
return r.db
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package dorm
|
|
@ -0,0 +1,76 @@
|
|||||||
|
package dorm
|
||||||
|
|
||||||
|
// Bool 复制 bool 对象,并返回复制体
|
||||||
|
func Bool(b bool) bool {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 复制 int8 对象,并返回复制体
|
||||||
|
func Uint8(ui uint8) uint8 {
|
||||||
|
return ui
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 复制 uint16 对象,并返回复制体
|
||||||
|
func Uint16(ui uint16) uint16 {
|
||||||
|
return ui
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 复制 uint32 对象,并返回复制体
|
||||||
|
func Uint32(ui uint32) uint32 {
|
||||||
|
return ui
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 复制 uint64 对象,并返回复制体
|
||||||
|
func Uint64(ui uint64) uint64 {
|
||||||
|
return ui
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 复制 int8 对象,并返回复制体
|
||||||
|
func Int8(i int8) int8 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 复制 int16 对象,并返回复制体
|
||||||
|
func Int16(i int16) int16 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 复制 int64 对象,并返回复制体
|
||||||
|
func Int32(i int32) int32 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 复制 int64 对象,并返回复制体
|
||||||
|
func Int64(i int64) int64 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 复制 float32 对象,并返回复制体
|
||||||
|
func Float32(f float32) float32 {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 复制 float64 对象,并返回复制体
|
||||||
|
func Float64(f float64) float64 {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// String 复制 string 对象,并返回复制体
|
||||||
|
func String(s string) string {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int 复制 int 对象,并返回复制体
|
||||||
|
func Int(i int) int {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint 复制 uint 对象,并返回复制体
|
||||||
|
func Uint(ui uint) uint {
|
||||||
|
return ui
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any 复制 any 对象,并返回复制体
|
||||||
|
func Any(a any) any {
|
||||||
|
return a
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import "github.com/upper/db/v4"
|
|
||||||
|
|
||||||
// UpperClient
|
|
||||||
// https://upper.io/
|
|
||||||
type UpperClient struct {
|
|
||||||
Db *db.Session // 驱动
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/upper/db/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetDb 获取驱动
|
|
||||||
func (c *UpperClient) GetDb() *db.Session {
|
|
||||||
return c.Db
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/upper/db/v4/adapter/mysql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewUpperMysqlClient(settings mysql.ConnectionURL) (*UpperClient, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c := &UpperClient{}
|
|
||||||
|
|
||||||
sess, err := mysql.Open(settings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
|
|
||||||
}
|
|
||||||
defer sess.Close()
|
|
||||||
|
|
||||||
c.Db = &sess
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package dorm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/upper/db/v4/adapter/postgresql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewUpperPostgresqlClient(settings postgresql.ConnectionURL) (*UpperClient, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c := &UpperClient{}
|
|
||||||
|
|
||||||
sess, err := postgresql.Open(settings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
|
|
||||||
}
|
|
||||||
defer sess.Close()
|
|
||||||
|
|
||||||
c.Db = &sess
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.prof
|
|
||||||
/.tags
|
|
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 Bastien Gysler
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
@ -1,82 +0,0 @@
|
|||||||
# goxml2json [![CircleCI](https://circleci.com/gh/basgys/goxml2json.svg?style=svg)](https://circleci.com/gh/basgys/goxml2json)
|
|
||||||
|
|
||||||
Go package that converts XML to JSON
|
|
||||||
|
|
||||||
### Install
|
|
||||||
|
|
||||||
go get -u github.com/basgys/goxml2json
|
|
||||||
|
|
||||||
### Importing
|
|
||||||
|
|
||||||
import github.com/basgys/goxml2json
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
**Code example**
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
xj "github.com/basgys/goxml2json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// xml is an io.Reader
|
|
||||||
xml := strings.NewReader(`<?xml version="1.0" encoding="UTF-8"?><hello>world</hello>`)
|
|
||||||
json, err := xj.Convert(xml)
|
|
||||||
if err != nil {
|
|
||||||
panic("That's embarrassing...")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(json.String())
|
|
||||||
// {"hello": "world"}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
**Input**
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<osm version="0.6" generator="CGImap 0.0.2">
|
|
||||||
<bounds minlat="54.0889580" minlon="12.2487570" maxlat="54.0913900" maxlon="12.2524800"/>
|
|
||||||
<foo>bar</foo>
|
|
||||||
</osm>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"osm": {
|
|
||||||
"-version": "0.6",
|
|
||||||
"-generator": "CGImap 0.0.2",
|
|
||||||
"bounds": {
|
|
||||||
"-minlat": "54.0889580",
|
|
||||||
"-minlon": "12.2487570",
|
|
||||||
"-maxlat": "54.0913900",
|
|
||||||
"-maxlon": "12.2524800"
|
|
||||||
},
|
|
||||||
"foo": "bar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
Feel free to contribute to this project if you want to fix/extend/improve it.
|
|
||||||
|
|
||||||
### Contributors
|
|
||||||
|
|
||||||
- [DirectX](https://github.com/directx)
|
|
||||||
- [samuelhug](https://github.com/samuelhug)
|
|
||||||
|
|
||||||
### TODO
|
|
||||||
|
|
||||||
* Extract data types in JSON (numbers, boolean, ...)
|
|
||||||
* Categorise errors
|
|
||||||
* Option to prettify the JSON output
|
|
||||||
* Benchmark
|
|
@ -1,25 +0,0 @@
|
|||||||
package xml2json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Convert converts the given XML document to JSON
|
|
||||||
func Convert(r io.Reader) (*bytes.Buffer, error) {
|
|
||||||
// Decode XML document
|
|
||||||
root := &Node{}
|
|
||||||
err := NewDecoder(r).Decode(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then encode it in JSON
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err = NewEncoder(buf).Encode(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
package xml2json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"io"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"golang.org/x/net/html/charset"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
attrPrefix = "-"
|
|
||||||
contentPrefix = "#"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Decoder reads and decodes XML objects from an input stream.
|
|
||||||
type Decoder struct {
|
|
||||||
r io.Reader
|
|
||||||
err error
|
|
||||||
attributePrefix string
|
|
||||||
contentPrefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
type element struct {
|
|
||||||
parent *element
|
|
||||||
n *Node
|
|
||||||
label string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) SetAttributePrefix(prefix string) {
|
|
||||||
dec.attributePrefix = prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) SetContentPrefix(prefix string) {
|
|
||||||
dec.contentPrefix = prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) DecodeWithCustomPrefixes(root *Node, contentPrefix string, attributePrefix string) error {
|
|
||||||
dec.contentPrefix = contentPrefix
|
|
||||||
dec.attributePrefix = attributePrefix
|
|
||||||
return dec.Decode(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder returns a new decoder that reads from r.
|
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
|
||||||
return &Decoder{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads the next JSON-encoded value from its
|
|
||||||
// input and stores it in the value pointed to by v.
|
|
||||||
func (dec *Decoder) Decode(root *Node) error {
|
|
||||||
|
|
||||||
if dec.contentPrefix == "" {
|
|
||||||
dec.contentPrefix = contentPrefix
|
|
||||||
}
|
|
||||||
if dec.attributePrefix == "" {
|
|
||||||
dec.attributePrefix = attrPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlDec := xml.NewDecoder(dec.r)
|
|
||||||
|
|
||||||
// That will convert the charset if the provided XML is non-UTF-8
|
|
||||||
xmlDec.CharsetReader = charset.NewReaderLabel
|
|
||||||
|
|
||||||
// Create first element from the root node
|
|
||||||
elem := &element{
|
|
||||||
parent: nil,
|
|
||||||
n: root,
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
t, _ := xmlDec.Token()
|
|
||||||
if t == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch se := t.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
// Build new a new current element and link it to its parent
|
|
||||||
elem = &element{
|
|
||||||
parent: elem,
|
|
||||||
n: &Node{},
|
|
||||||
label: se.Name.Local,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract attributes as children
|
|
||||||
for _, a := range se.Attr {
|
|
||||||
elem.n.AddChild(dec.attributePrefix+a.Name.Local, &Node{Data: a.Value})
|
|
||||||
}
|
|
||||||
case xml.CharData:
|
|
||||||
// Extract XML data (if any)
|
|
||||||
elem.n.Data = trimNonGraphic(string(xml.CharData(se)))
|
|
||||||
case xml.EndElement:
|
|
||||||
// And add it to its parent list
|
|
||||||
if elem.parent != nil {
|
|
||||||
elem.parent.n.AddChild(elem.label, elem.n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then change the current element to its parent
|
|
||||||
elem = elem.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// trimNonGraphic returns a slice of the string s, with all leading and trailing
|
|
||||||
// non graphic characters and spaces removed.
|
|
||||||
//
|
|
||||||
// Graphic characters include letters, marks, numbers, punctuation, symbols,
|
|
||||||
// and spaces, from categories L, M, N, P, S, Zs.
|
|
||||||
// Spacing characters are set by category Z and property Pattern_White_Space.
|
|
||||||
func trimNonGraphic(s string) string {
|
|
||||||
if s == "" {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
var first *int
|
|
||||||
var last int
|
|
||||||
for i, r := range []rune(s) {
|
|
||||||
if !unicode.IsGraphic(r) || unicode.IsSpace(r) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if first == nil {
|
|
||||||
f := i // copy i
|
|
||||||
first = &f
|
|
||||||
last = i
|
|
||||||
} else {
|
|
||||||
last = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If first is nil, it means there are no graphic characters
|
|
||||||
if first == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return string([]rune(s)[*first : last+1])
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
// Package xml2json is an XML to JSON converter
|
|
||||||
package xml2json
|
|
@ -1,197 +0,0 @@
|
|||||||
package xml2json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An Encoder writes JSON objects to an output stream.
|
|
||||||
type Encoder struct {
|
|
||||||
w io.Writer
|
|
||||||
err error
|
|
||||||
contentPrefix string
|
|
||||||
attributePrefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder returns a new encoder that writes to w.
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{w: w}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) SetAttributePrefix(prefix string) {
|
|
||||||
enc.attributePrefix = prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) SetContentPrefix(prefix string) {
|
|
||||||
enc.contentPrefix = prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) EncodeWithCustomPrefixes(root *Node, contentPrefix string, attributePrefix string) error {
|
|
||||||
enc.contentPrefix = contentPrefix
|
|
||||||
enc.attributePrefix = attributePrefix
|
|
||||||
return enc.Encode(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes the JSON encoding of v to the stream
|
|
||||||
func (enc *Encoder) Encode(root *Node) error {
|
|
||||||
if enc.err != nil {
|
|
||||||
return enc.err
|
|
||||||
}
|
|
||||||
if root == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if enc.contentPrefix == "" {
|
|
||||||
enc.contentPrefix = contentPrefix
|
|
||||||
}
|
|
||||||
if enc.attributePrefix == "" {
|
|
||||||
enc.attributePrefix = attrPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
enc.err = enc.format(root, 0)
|
|
||||||
|
|
||||||
// Terminate each value with a newline.
|
|
||||||
// This makes the output look a little nicer
|
|
||||||
// when debugging, and some kind of space
|
|
||||||
// is required if the encoded value was a number,
|
|
||||||
// so that the reader knows there aren't more
|
|
||||||
// digits coming.
|
|
||||||
enc.write("\n")
|
|
||||||
|
|
||||||
return enc.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) format(n *Node, lvl int) error {
|
|
||||||
if n.IsComplex() {
|
|
||||||
enc.write("{")
|
|
||||||
|
|
||||||
// Add data as an additional attibute (if any)
|
|
||||||
if len(n.Data) > 0 {
|
|
||||||
enc.write("\"")
|
|
||||||
enc.write(enc.contentPrefix)
|
|
||||||
enc.write("content")
|
|
||||||
enc.write("\": ")
|
|
||||||
enc.write(sanitiseString(n.Data))
|
|
||||||
enc.write(", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
tot := len(n.Children)
|
|
||||||
for label, children := range n.Children {
|
|
||||||
enc.write("\"")
|
|
||||||
enc.write(label)
|
|
||||||
enc.write("\": ")
|
|
||||||
|
|
||||||
if len(children) > 1 {
|
|
||||||
// Array
|
|
||||||
enc.write("[")
|
|
||||||
for j, c := range children {
|
|
||||||
enc.format(c, lvl+1)
|
|
||||||
|
|
||||||
if j < len(children)-1 {
|
|
||||||
enc.write(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enc.write("]")
|
|
||||||
} else {
|
|
||||||
// Map
|
|
||||||
enc.format(children[0], lvl+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < tot-1 {
|
|
||||||
enc.write(", ")
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
enc.write("}")
|
|
||||||
} else {
|
|
||||||
// TODO : Extract data type
|
|
||||||
enc.write(sanitiseString(n.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) write(s string) {
|
|
||||||
enc.w.Write([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://golang.org/src/encoding/json/encode.go?s=5584:5627#L788
|
|
||||||
var hex = "0123456789abcdef"
|
|
||||||
|
|
||||||
func sanitiseString(s string) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
buf.WriteByte('"')
|
|
||||||
start := 0
|
|
||||||
for i := 0; i < len(s); {
|
|
||||||
if b := s[i]; b < utf8.RuneSelf {
|
|
||||||
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if start < i {
|
|
||||||
buf.WriteString(s[start:i])
|
|
||||||
}
|
|
||||||
switch b {
|
|
||||||
case '\\', '"':
|
|
||||||
buf.WriteByte('\\')
|
|
||||||
buf.WriteByte(b)
|
|
||||||
case '\n':
|
|
||||||
buf.WriteByte('\\')
|
|
||||||
buf.WriteByte('n')
|
|
||||||
case '\r':
|
|
||||||
buf.WriteByte('\\')
|
|
||||||
buf.WriteByte('r')
|
|
||||||
case '\t':
|
|
||||||
buf.WriteByte('\\')
|
|
||||||
buf.WriteByte('t')
|
|
||||||
default:
|
|
||||||
// This encodes bytes < 0x20 except for \n and \r,
|
|
||||||
// as well as <, > and &. The latter are escaped because they
|
|
||||||
// can lead to security holes when user-controlled strings
|
|
||||||
// are rendered into JSON and served to some browsers.
|
|
||||||
buf.WriteString(`\u00`)
|
|
||||||
buf.WriteByte(hex[b>>4])
|
|
||||||
buf.WriteByte(hex[b&0xF])
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
start = i
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c, size := utf8.DecodeRuneInString(s[i:])
|
|
||||||
if c == utf8.RuneError && size == 1 {
|
|
||||||
if start < i {
|
|
||||||
buf.WriteString(s[start:i])
|
|
||||||
}
|
|
||||||
buf.WriteString(`\ufffd`)
|
|
||||||
i += size
|
|
||||||
start = i
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// U+2028 is LINE SEPARATOR.
|
|
||||||
// U+2029 is PARAGRAPH SEPARATOR.
|
|
||||||
// They are both technically valid characters in JSON strings,
|
|
||||||
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
|
||||||
// and can lead to security holes there. It is valid JSON to
|
|
||||||
// escape them, so we do so unconditionally.
|
|
||||||
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
|
||||||
if c == '\u2028' || c == '\u2029' {
|
|
||||||
if start < i {
|
|
||||||
buf.WriteString(s[start:i])
|
|
||||||
}
|
|
||||||
buf.WriteString(`\u202`)
|
|
||||||
buf.WriteByte(hex[c&0xF])
|
|
||||||
i += size
|
|
||||||
start = i
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i += size
|
|
||||||
}
|
|
||||||
if start < len(s) {
|
|
||||||
buf.WriteString(s[start:])
|
|
||||||
}
|
|
||||||
buf.WriteByte('"')
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package xml2json
|
|
||||||
|
|
||||||
// Node is a data element on a tree
|
|
||||||
type Node struct {
|
|
||||||
Children map[string]Nodes
|
|
||||||
Data string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes is a list of nodes
|
|
||||||
type Nodes []*Node
|
|
||||||
|
|
||||||
// AddChild appends a node to the list of children
|
|
||||||
func (n *Node) AddChild(s string, c *Node) {
|
|
||||||
// Lazy lazy
|
|
||||||
if n.Children == nil {
|
|
||||||
n.Children = map[string]Nodes{}
|
|
||||||
}
|
|
||||||
|
|
||||||
n.Children[s] = append(n.Children[s], c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsComplex returns whether it is a complex type (has children)
|
|
||||||
func (n *Node) IsComplex() bool {
|
|
||||||
return len(n.Children) > 0
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
Copyright 2014 astaxie
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
@ -1,6 +0,0 @@
|
|||||||
package clauses
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExprSep = "__"
|
|
||||||
ExprDot = "."
|
|
||||||
)
|
|
@ -1,104 +0,0 @@
|
|||||||
package order_clause
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm/clauses"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Sort int8
|
|
||||||
|
|
||||||
const (
|
|
||||||
None Sort = 0
|
|
||||||
Ascending Sort = 1
|
|
||||||
Descending Sort = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option func(order *Order)
|
|
||||||
|
|
||||||
type Order struct {
|
|
||||||
column string
|
|
||||||
sort Sort
|
|
||||||
isRaw bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func Clause(options ...Option) *Order {
|
|
||||||
o := &Order{}
|
|
||||||
for _, option := range options {
|
|
||||||
option(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Order) GetColumn() string {
|
|
||||||
return o.column
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Order) GetSort() Sort {
|
|
||||||
return o.sort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Order) SortString() string {
|
|
||||||
switch o.GetSort() {
|
|
||||||
case Ascending:
|
|
||||||
return "ASC"
|
|
||||||
case Descending:
|
|
||||||
return "DESC"
|
|
||||||
}
|
|
||||||
|
|
||||||
return ``
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Order) IsRaw() bool {
|
|
||||||
return o.isRaw
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseOrder(expressions ...string) []*Order {
|
|
||||||
var orders []*Order
|
|
||||||
for _, expression := range expressions {
|
|
||||||
sort := Ascending
|
|
||||||
column := strings.ReplaceAll(expression, clauses.ExprSep, clauses.ExprDot)
|
|
||||||
if column[0] == '-' {
|
|
||||||
sort = Descending
|
|
||||||
column = column[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
orders = append(orders, &Order{
|
|
||||||
column: column,
|
|
||||||
sort: sort,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return orders
|
|
||||||
}
|
|
||||||
|
|
||||||
func Column(column string) Option {
|
|
||||||
return func(order *Order) {
|
|
||||||
order.column = strings.ReplaceAll(column, clauses.ExprSep, clauses.ExprDot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sort(sort Sort) Option {
|
|
||||||
return func(order *Order) {
|
|
||||||
order.sort = sort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SortAscending() Option {
|
|
||||||
return sort(Ascending)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SortDescending() Option {
|
|
||||||
return sort(Descending)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SortNone() Option {
|
|
||||||
return sort(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Raw() Option {
|
|
||||||
return func(order *Order) {
|
|
||||||
order.isRaw = true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type commander interface {
|
|
||||||
Parse([]string)
|
|
||||||
Run() error
|
|
||||||
}
|
|
||||||
|
|
||||||
var commands = make(map[string]commander)
|
|
||||||
|
|
||||||
// print help.
|
|
||||||
func printHelp(errs ...string) {
|
|
||||||
content := `orm command usage:
|
|
||||||
|
|
||||||
syncdb - auto create tables
|
|
||||||
sqlall - print sql of create tables
|
|
||||||
help - print this help
|
|
||||||
`
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
fmt.Println(errs[0])
|
|
||||||
}
|
|
||||||
fmt.Println(content)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCommand listens for orm command and runs if command arguments have been passed.
|
|
||||||
func RunCommand() {
|
|
||||||
if len(os.Args) < 2 || os.Args[1] != "orm" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
BootStrap()
|
|
||||||
|
|
||||||
args := argString(os.Args[2:])
|
|
||||||
name := args.Get(0)
|
|
||||||
|
|
||||||
if name == "help" {
|
|
||||||
printHelp()
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd, ok := commands[name]; ok {
|
|
||||||
cmd.Parse(os.Args[3:])
|
|
||||||
cmd.Run()
|
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
if name == "" {
|
|
||||||
printHelp()
|
|
||||||
} else {
|
|
||||||
printHelp(fmt.Sprintf("unknown command %s", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync database struct command interface.
|
|
||||||
type commandSyncDb struct {
|
|
||||||
al *alias
|
|
||||||
force bool
|
|
||||||
verbose bool
|
|
||||||
noInfo bool
|
|
||||||
rtOnError bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the orm command line arguments.
|
|
||||||
func (d *commandSyncDb) Parse(args []string) {
|
|
||||||
var name string
|
|
||||||
|
|
||||||
flagSet := flag.NewFlagSet("orm command: syncdb", flag.ExitOnError)
|
|
||||||
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
|
||||||
flagSet.BoolVar(&d.force, "force", false, "drop tables before create")
|
|
||||||
flagSet.BoolVar(&d.verbose, "v", false, "verbose info")
|
|
||||||
flagSet.Parse(args)
|
|
||||||
|
|
||||||
d.al = getDbAlias(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run orm line command.
|
|
||||||
func (d *commandSyncDb) Run() error {
|
|
||||||
var drops []string
|
|
||||||
var err error
|
|
||||||
if d.force {
|
|
||||||
drops, err = defaultModelCache.getDbDropSQL(d.al)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db := d.al.DB
|
|
||||||
|
|
||||||
if d.force && len(drops) > 0 {
|
|
||||||
for i, mi := range defaultModelCache.allOrdered() {
|
|
||||||
query := drops[i]
|
|
||||||
if !d.noInfo {
|
|
||||||
fmt.Printf("drop table `%s`\n", mi.table)
|
|
||||||
}
|
|
||||||
_, err := db.Exec(query)
|
|
||||||
if d.verbose {
|
|
||||||
fmt.Printf(" %s\n\n", query)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if d.rtOnError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s\n", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createQueries, indexes, err := defaultModelCache.getDbCreateSQL(d.al)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tables, err := d.al.DbBaser.GetTables(db)
|
|
||||||
if err != nil {
|
|
||||||
if d.rtOnError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
for i, mi := range defaultModelCache.allOrdered() {
|
|
||||||
|
|
||||||
if !isApplicableTableForDB(mi.addrField, d.al.Name) {
|
|
||||||
fmt.Printf("table `%s` is not applicable to database '%s'\n", mi.table, d.al.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tables[mi.table] {
|
|
||||||
if !d.noInfo {
|
|
||||||
fmt.Printf("table `%s` already exists, skip\n", mi.table)
|
|
||||||
}
|
|
||||||
|
|
||||||
var fields []*fieldInfo
|
|
||||||
columns, err := d.al.DbBaser.GetColumns(ctx, db, mi.table)
|
|
||||||
if err != nil {
|
|
||||||
if d.rtOnError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range mi.fields.fieldsDB {
|
|
||||||
if _, ok := columns[fi.column]; !ok {
|
|
||||||
fields = append(fields, fi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range fields {
|
|
||||||
query := getColumnAddQuery(d.al, fi)
|
|
||||||
|
|
||||||
if !d.noInfo {
|
|
||||||
fmt.Printf("add column `%s` for table `%s`\n", fi.fullName, mi.table)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := db.Exec(query)
|
|
||||||
if d.verbose {
|
|
||||||
fmt.Printf(" %s\n", query)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if d.rtOnError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s\n", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, idx := range indexes[mi.table] {
|
|
||||||
if !d.al.DbBaser.IndexExists(ctx, db, idx.Table, idx.Name) {
|
|
||||||
if !d.noInfo {
|
|
||||||
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := idx.SQL
|
|
||||||
_, err := db.Exec(query)
|
|
||||||
if d.verbose {
|
|
||||||
fmt.Printf(" %s\n", query)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if d.rtOnError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s\n", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !d.noInfo {
|
|
||||||
fmt.Printf("create table `%s` \n", mi.table)
|
|
||||||
}
|
|
||||||
|
|
||||||
queries := []string{createQueries[i]}
|
|
||||||
for _, idx := range indexes[mi.table] {
|
|
||||||
queries = append(queries, idx.SQL)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, query := range queries {
|
|
||||||
_, err := db.Exec(query)
|
|
||||||
if d.verbose {
|
|
||||||
query = " " + strings.Join(strings.Split(query, "\n"), "\n ")
|
|
||||||
fmt.Println(query)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if d.rtOnError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s\n", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if d.verbose {
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// database creation commander interface implement.
|
|
||||||
type commandSQLAll struct {
|
|
||||||
al *alias
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse orm command line arguments.
|
|
||||||
func (d *commandSQLAll) Parse(args []string) {
|
|
||||||
var name string
|
|
||||||
|
|
||||||
flagSet := flag.NewFlagSet("orm command: sqlall", flag.ExitOnError)
|
|
||||||
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
|
||||||
flagSet.Parse(args)
|
|
||||||
|
|
||||||
d.al = getDbAlias(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run orm line command.
|
|
||||||
func (d *commandSQLAll) Run() error {
|
|
||||||
createQueries, indexes, err := defaultModelCache.getDbCreateSQL(d.al)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var all []string
|
|
||||||
for i, mi := range defaultModelCache.allOrdered() {
|
|
||||||
queries := []string{createQueries[i]}
|
|
||||||
for _, idx := range indexes[mi.table] {
|
|
||||||
queries = append(queries, idx.SQL)
|
|
||||||
}
|
|
||||||
sql := strings.Join(queries, "\n")
|
|
||||||
all = append(all, sql)
|
|
||||||
}
|
|
||||||
fmt.Println(strings.Join(all, "\n\n"))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commands["syncdb"] = new(commandSyncDb)
|
|
||||||
commands["sqlall"] = new(commandSQLAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunSyncdb run syncdb command line.
|
|
||||||
// name: Table's alias name (default is "default")
|
|
||||||
// force: Run the next sql command even if the current gave an error
|
|
||||||
// verbose: Print all information, useful for debugging
|
|
||||||
func RunSyncdb(name string, force bool, verbose bool) error {
|
|
||||||
BootStrap()
|
|
||||||
|
|
||||||
al := getDbAlias(name)
|
|
||||||
cmd := new(commandSyncDb)
|
|
||||||
cmd.al = al
|
|
||||||
cmd.force = force
|
|
||||||
cmd.noInfo = !verbose
|
|
||||||
cmd.verbose = verbose
|
|
||||||
cmd.rtOnError = true
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dbIndex struct {
|
|
||||||
Table string
|
|
||||||
Name string
|
|
||||||
SQL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// get database column type string.
|
|
||||||
func getColumnTyp(al *alias, fi *fieldInfo) (col string) {
|
|
||||||
T := al.DbBaser.DbTypes()
|
|
||||||
fieldType := fi.fieldType
|
|
||||||
fieldSize := fi.size
|
|
||||||
|
|
||||||
checkColumn:
|
|
||||||
switch fieldType {
|
|
||||||
case TypeBooleanField:
|
|
||||||
col = T["bool"]
|
|
||||||
case TypeVarCharField:
|
|
||||||
if al.Driver == DRPostgres && fi.toText {
|
|
||||||
col = T["string-text"]
|
|
||||||
} else {
|
|
||||||
col = fmt.Sprintf(T["string"], fieldSize)
|
|
||||||
}
|
|
||||||
case TypeCharField:
|
|
||||||
col = fmt.Sprintf(T["string-char"], fieldSize)
|
|
||||||
case TypeTextField:
|
|
||||||
col = T["string-text"]
|
|
||||||
case TypeTimeField:
|
|
||||||
col = T["time.Time-clock"]
|
|
||||||
case TypeDateField:
|
|
||||||
col = T["time.Time-date"]
|
|
||||||
case TypeDateTimeField:
|
|
||||||
// the precision of sqlite is not implemented
|
|
||||||
if al.Driver == 2 || fi.timePrecision == nil {
|
|
||||||
col = T["time.Time"]
|
|
||||||
} else {
|
|
||||||
s := T["time.Time-precision"]
|
|
||||||
col = fmt.Sprintf(s, *fi.timePrecision)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TypeBitField:
|
|
||||||
col = T["int8"]
|
|
||||||
case TypeSmallIntegerField:
|
|
||||||
col = T["int16"]
|
|
||||||
case TypeIntegerField:
|
|
||||||
col = T["int32"]
|
|
||||||
case TypeBigIntegerField:
|
|
||||||
if al.Driver == DRSqlite {
|
|
||||||
fieldType = TypeIntegerField
|
|
||||||
goto checkColumn
|
|
||||||
}
|
|
||||||
col = T["int64"]
|
|
||||||
case TypePositiveBitField:
|
|
||||||
col = T["uint8"]
|
|
||||||
case TypePositiveSmallIntegerField:
|
|
||||||
col = T["uint16"]
|
|
||||||
case TypePositiveIntegerField:
|
|
||||||
col = T["uint32"]
|
|
||||||
case TypePositiveBigIntegerField:
|
|
||||||
col = T["uint64"]
|
|
||||||
case TypeFloatField:
|
|
||||||
col = T["float64"]
|
|
||||||
case TypeDecimalField:
|
|
||||||
s := T["float64-decimal"]
|
|
||||||
if !strings.Contains(s, "%d") {
|
|
||||||
col = s
|
|
||||||
} else {
|
|
||||||
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
|
||||||
}
|
|
||||||
case TypeJSONField:
|
|
||||||
if al.Driver != DRPostgres {
|
|
||||||
fieldType = TypeVarCharField
|
|
||||||
goto checkColumn
|
|
||||||
}
|
|
||||||
col = T["json"]
|
|
||||||
case TypeJsonbField:
|
|
||||||
if al.Driver != DRPostgres {
|
|
||||||
fieldType = TypeVarCharField
|
|
||||||
goto checkColumn
|
|
||||||
}
|
|
||||||
col = T["jsonb"]
|
|
||||||
case RelForeignKey, RelOneToOne:
|
|
||||||
fieldType = fi.relModelInfo.fields.pk.fieldType
|
|
||||||
fieldSize = fi.relModelInfo.fields.pk.size
|
|
||||||
goto checkColumn
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create alter sql string.
|
|
||||||
func getColumnAddQuery(al *alias, fi *fieldInfo) string {
|
|
||||||
Q := al.DbBaser.TableQuote()
|
|
||||||
typ := getColumnTyp(al, fi)
|
|
||||||
|
|
||||||
if !fi.null {
|
|
||||||
typ += " " + "NOT NULL"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s",
|
|
||||||
Q, fi.mi.table, Q,
|
|
||||||
Q, fi.column, Q,
|
|
||||||
typ, getColumnDefault(fi),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands
|
|
||||||
func getColumnDefault(fi *fieldInfo) string {
|
|
||||||
var v, t, d string
|
|
||||||
|
|
||||||
// Skip default attribute if field is in relations
|
|
||||||
if fi.rel || fi.reverse {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
t = " DEFAULT '%s' "
|
|
||||||
|
|
||||||
// These defaults will be useful if there no config value orm:"default" and NOT NULL is on
|
|
||||||
switch fi.fieldType {
|
|
||||||
case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField:
|
|
||||||
return v
|
|
||||||
|
|
||||||
case TypeBitField, TypeSmallIntegerField, TypeIntegerField,
|
|
||||||
TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField,
|
|
||||||
TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField,
|
|
||||||
TypeDecimalField:
|
|
||||||
t = " DEFAULT %s "
|
|
||||||
d = "0"
|
|
||||||
case TypeBooleanField:
|
|
||||||
t = " DEFAULT %s "
|
|
||||||
d = "FALSE"
|
|
||||||
case TypeJSONField, TypeJsonbField:
|
|
||||||
d = "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.colDefault {
|
|
||||||
if !fi.initial.Exist() {
|
|
||||||
v = fmt.Sprintf(t, "")
|
|
||||||
} else {
|
|
||||||
v = fmt.Sprintf(t, fi.initial.String())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !fi.null {
|
|
||||||
v = fmt.Sprintf(t, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,599 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DriverType database driver constant int.
|
|
||||||
type DriverType int
|
|
||||||
|
|
||||||
// Enum the Database driver
|
|
||||||
const (
|
|
||||||
_ DriverType = iota // int enum type
|
|
||||||
DRMySQL // mysql
|
|
||||||
DRSqlite // sqlite
|
|
||||||
DROracle // oracle
|
|
||||||
DRPostgres // pgsql
|
|
||||||
DRTiDB // TiDB
|
|
||||||
)
|
|
||||||
|
|
||||||
// database driver string.
|
|
||||||
type driver string
|
|
||||||
|
|
||||||
// get type constant int of current driver..
|
|
||||||
func (d driver) Type() DriverType {
|
|
||||||
a, _ := dataBaseCache.get(string(d))
|
|
||||||
return a.Driver
|
|
||||||
}
|
|
||||||
|
|
||||||
// get name of current driver
|
|
||||||
func (d driver) Name() string {
|
|
||||||
return string(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check driver iis implemented Driver interface or not.
|
|
||||||
var _ Driver = new(driver)
|
|
||||||
|
|
||||||
var (
|
|
||||||
dataBaseCache = &_dbCache{cache: make(map[string]*alias)}
|
|
||||||
drivers = map[string]DriverType{
|
|
||||||
"mysql": DRMySQL,
|
|
||||||
"postgres": DRPostgres,
|
|
||||||
"sqlite3": DRSqlite,
|
|
||||||
"tidb": DRTiDB,
|
|
||||||
"oracle": DROracle,
|
|
||||||
"oci8": DROracle, // github.com/mattn/go-oci8
|
|
||||||
"ora": DROracle, // https://github.com/rana/ora
|
|
||||||
}
|
|
||||||
dbBasers = map[DriverType]dbBaser{
|
|
||||||
DRMySQL: newdbBaseMysql(),
|
|
||||||
DRSqlite: newdbBaseSqlite(),
|
|
||||||
DROracle: newdbBaseOracle(),
|
|
||||||
DRPostgres: newdbBasePostgres(),
|
|
||||||
DRTiDB: newdbBaseTidb(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// database alias cacher.
|
|
||||||
type _dbCache struct {
|
|
||||||
mux sync.RWMutex
|
|
||||||
cache map[string]*alias
|
|
||||||
}
|
|
||||||
|
|
||||||
// add database alias with original name.
|
|
||||||
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
|
||||||
ac.mux.Lock()
|
|
||||||
defer ac.mux.Unlock()
|
|
||||||
if _, ok := ac.cache[name]; !ok {
|
|
||||||
ac.cache[name] = al
|
|
||||||
added = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get database alias if cached.
|
|
||||||
func (ac *_dbCache) get(name string) (al *alias, ok bool) {
|
|
||||||
ac.mux.RLock()
|
|
||||||
defer ac.mux.RUnlock()
|
|
||||||
al, ok = ac.cache[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get default alias.
|
|
||||||
func (ac *_dbCache) getDefault() (al *alias) {
|
|
||||||
al, _ = ac.get("default")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type DB struct {
|
|
||||||
*sync.RWMutex
|
|
||||||
DB *sql.DB
|
|
||||||
stmtDecorators *lru.Cache
|
|
||||||
stmtDecoratorsLimit int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ dbQuerier = new(DB)
|
|
||||||
_ txer = new(DB)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DB) Begin() (*sql.Tx, error) {
|
|
||||||
return d.DB.Begin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
|
||||||
return d.DB.BeginTx(ctx, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// su must call release to release *sql.Stmt after using
|
|
||||||
func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) {
|
|
||||||
d.RLock()
|
|
||||||
c, ok := d.stmtDecorators.Get(query)
|
|
||||||
if ok {
|
|
||||||
c.(*stmtDecorator).acquire()
|
|
||||||
d.RUnlock()
|
|
||||||
return c.(*stmtDecorator), nil
|
|
||||||
}
|
|
||||||
d.RUnlock()
|
|
||||||
|
|
||||||
d.Lock()
|
|
||||||
c, ok = d.stmtDecorators.Get(query)
|
|
||||||
if ok {
|
|
||||||
c.(*stmtDecorator).acquire()
|
|
||||||
d.Unlock()
|
|
||||||
return c.(*stmtDecorator), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := d.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
d.Unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sd := newStmtDecorator(stmt)
|
|
||||||
sd.acquire()
|
|
||||||
d.stmtDecorators.Add(query, sd)
|
|
||||||
d.Unlock()
|
|
||||||
|
|
||||||
return sd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) Prepare(query string) (*sql.Stmt, error) {
|
|
||||||
return d.DB.Prepare(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
|
||||||
return d.DB.PrepareContext(ctx, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
|
||||||
return d.ExecContext(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
|
||||||
if d.stmtDecorators == nil {
|
|
||||||
return d.DB.ExecContext(ctx, query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sd, err := d.getStmtDecorator(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stmt := sd.getStmt()
|
|
||||||
defer sd.release()
|
|
||||||
return stmt.ExecContext(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
|
||||||
return d.QueryContext(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
|
||||||
if d.stmtDecorators == nil {
|
|
||||||
return d.DB.QueryContext(ctx, query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sd, err := d.getStmtDecorator(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stmt := sd.getStmt()
|
|
||||||
defer sd.release()
|
|
||||||
return stmt.QueryContext(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
|
||||||
return d.QueryRowContext(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
|
||||||
if d.stmtDecorators == nil {
|
|
||||||
return d.DB.QueryRowContext(ctx, query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sd, err := d.getStmtDecorator(query)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
stmt := sd.getStmt()
|
|
||||||
defer sd.release()
|
|
||||||
return stmt.QueryRowContext(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TxDB struct {
|
|
||||||
tx *sql.Tx
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ dbQuerier = new(TxDB)
|
|
||||||
_ txEnder = new(TxDB)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *TxDB) Commit() error {
|
|
||||||
return t.tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) Rollback() error {
|
|
||||||
return t.tx.Rollback()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) RollbackUnlessCommit() error {
|
|
||||||
err := t.tx.Rollback()
|
|
||||||
if err != sql.ErrTxDone {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ dbQuerier = new(TxDB)
|
|
||||||
_ txEnder = new(TxDB)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *TxDB) Prepare(query string) (*sql.Stmt, error) {
|
|
||||||
return t.PrepareContext(context.Background(), query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
|
||||||
return t.tx.PrepareContext(ctx, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
|
||||||
return t.ExecContext(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
|
||||||
return t.tx.ExecContext(ctx, query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
|
||||||
return t.QueryContext(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
|
||||||
return t.tx.QueryContext(ctx, query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) QueryRow(query string, args ...interface{}) *sql.Row {
|
|
||||||
return t.QueryRowContext(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TxDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
|
||||||
return t.tx.QueryRowContext(ctx, query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type alias struct {
|
|
||||||
Name string
|
|
||||||
Driver DriverType
|
|
||||||
DriverName string
|
|
||||||
DataSource string
|
|
||||||
MaxIdleConns int
|
|
||||||
MaxOpenConns int
|
|
||||||
ConnMaxLifetime time.Duration
|
|
||||||
StmtCacheSize int
|
|
||||||
DB *DB
|
|
||||||
DbBaser dbBaser
|
|
||||||
TZ *time.Location
|
|
||||||
Engine string
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectTZ(al *alias) {
|
|
||||||
// orm timezone system match database
|
|
||||||
// default use Local
|
|
||||||
al.TZ = DefaultTimeLoc
|
|
||||||
|
|
||||||
if al.DriverName == "sphinx" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch al.Driver {
|
|
||||||
case DRMySQL:
|
|
||||||
row := al.DB.QueryRow("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)")
|
|
||||||
var tz string
|
|
||||||
row.Scan(&tz)
|
|
||||||
if len(tz) >= 8 {
|
|
||||||
if tz[0] != '-' {
|
|
||||||
tz = "+" + tz
|
|
||||||
}
|
|
||||||
t, err := time.Parse("-07:00:00", tz)
|
|
||||||
if err == nil {
|
|
||||||
if t.Location().String() != "" {
|
|
||||||
al.TZ = t.Location()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get default engine from current database
|
|
||||||
row = al.DB.QueryRow("SELECT ENGINE, TRANSACTIONS FROM information_schema.engines WHERE SUPPORT = 'DEFAULT'")
|
|
||||||
var engine string
|
|
||||||
var tx bool
|
|
||||||
row.Scan(&engine, &tx)
|
|
||||||
|
|
||||||
if engine != "" {
|
|
||||||
al.Engine = engine
|
|
||||||
} else {
|
|
||||||
al.Engine = "INNODB"
|
|
||||||
}
|
|
||||||
|
|
||||||
case DRSqlite, DROracle:
|
|
||||||
al.TZ = time.UTC
|
|
||||||
|
|
||||||
case DRPostgres:
|
|
||||||
row := al.DB.QueryRow("SELECT current_setting('TIMEZONE')")
|
|
||||||
var tz string
|
|
||||||
row.Scan(&tz)
|
|
||||||
loc, err := time.LoadLocation(tz)
|
|
||||||
if err == nil {
|
|
||||||
al.TZ = loc
|
|
||||||
} else {
|
|
||||||
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) {
|
|
||||||
existErr := fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
|
|
||||||
if _, ok := dataBaseCache.get(aliasName); ok {
|
|
||||||
return nil, existErr
|
|
||||||
}
|
|
||||||
|
|
||||||
al, err := newAliasWithDb(aliasName, driverName, db, params...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dataBaseCache.add(aliasName, al) {
|
|
||||||
return nil, existErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return al, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAliasWithDb(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) {
|
|
||||||
al := &alias{}
|
|
||||||
al.DB = &DB{
|
|
||||||
RWMutex: new(sync.RWMutex),
|
|
||||||
DB: db,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range params {
|
|
||||||
p(al)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stmtCache *lru.Cache
|
|
||||||
var stmtCacheSize int
|
|
||||||
|
|
||||||
if al.StmtCacheSize > 0 {
|
|
||||||
_stmtCache, errC := newStmtDecoratorLruWithEvict(al.StmtCacheSize)
|
|
||||||
if errC != nil {
|
|
||||||
return nil, errC
|
|
||||||
} else {
|
|
||||||
stmtCache = _stmtCache
|
|
||||||
stmtCacheSize = al.StmtCacheSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
al.Name = aliasName
|
|
||||||
al.DriverName = driverName
|
|
||||||
al.DB.stmtDecorators = stmtCache
|
|
||||||
al.DB.stmtDecoratorsLimit = stmtCacheSize
|
|
||||||
|
|
||||||
if dr, ok := drivers[driverName]; ok {
|
|
||||||
al.DbBaser = dbBasers[dr]
|
|
||||||
al.Driver = dr
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("driver name `%s` have not registered", driverName)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := db.Ping()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
detectTZ(al)
|
|
||||||
|
|
||||||
return al, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name
|
|
||||||
// Deprecated you should not use this, we will remove it in the future
|
|
||||||
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
|
||||||
al := getDbAlias(aliasName)
|
|
||||||
al.SetMaxIdleConns(maxIdleConns)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
|
||||||
// Deprecated you should not use this, we will remove it in the future
|
|
||||||
func SetMaxOpenConns(aliasName string, maxOpenConns int) {
|
|
||||||
al := getDbAlias(aliasName)
|
|
||||||
al.SetMaxOpenConns(maxOpenConns)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name
|
|
||||||
func (al *alias) SetMaxIdleConns(maxIdleConns int) {
|
|
||||||
al.MaxIdleConns = maxIdleConns
|
|
||||||
al.DB.DB.SetMaxIdleConns(maxIdleConns)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
|
||||||
func (al *alias) SetMaxOpenConns(maxOpenConns int) {
|
|
||||||
al.MaxOpenConns = maxOpenConns
|
|
||||||
al.DB.DB.SetMaxOpenConns(maxOpenConns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (al *alias) SetConnMaxLifetime(lifeTime time.Duration) {
|
|
||||||
al.ConnMaxLifetime = lifeTime
|
|
||||||
al.DB.DB.SetConnMaxLifetime(lifeTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAliasWthDB add a aliasName for the drivename
|
|
||||||
func AddAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) error {
|
|
||||||
_, err := addAliasWthDB(aliasName, driverName, db, params...)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args.
|
|
||||||
func RegisterDataBase(aliasName, driverName, dataSource string, params ...DBOption) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
db *sql.DB
|
|
||||||
al *alias
|
|
||||||
)
|
|
||||||
|
|
||||||
db, err = sql.Open(driverName, dataSource)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
|
|
||||||
al, err = addAliasWthDB(aliasName, driverName, db, params...)
|
|
||||||
if err != nil {
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
|
|
||||||
al.DataSource = dataSource
|
|
||||||
|
|
||||||
end:
|
|
||||||
if err != nil {
|
|
||||||
if db != nil {
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
DebugLog.Println(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
|
||||||
func RegisterDriver(driverName string, typ DriverType) error {
|
|
||||||
if t, ok := drivers[driverName]; !ok {
|
|
||||||
drivers[driverName] = typ
|
|
||||||
} else {
|
|
||||||
if t != typ {
|
|
||||||
return fmt.Errorf("driverName `%s` db driver already registered and is other type", driverName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDataBaseTZ Change the database default used timezone
|
|
||||||
func SetDataBaseTZ(aliasName string, tz *time.Location) error {
|
|
||||||
if al, ok := dataBaseCache.get(aliasName); ok {
|
|
||||||
al.TZ = tz
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("DataBase alias name `%s` not registered", aliasName)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDB Get *sql.DB from registered database by db alias name.
|
|
||||||
// Use "default" as alias name if you not set.
|
|
||||||
func GetDB(aliasNames ...string) (*sql.DB, error) {
|
|
||||||
var name string
|
|
||||||
if len(aliasNames) > 0 {
|
|
||||||
name = aliasNames[0]
|
|
||||||
} else {
|
|
||||||
name = "default"
|
|
||||||
}
|
|
||||||
al, ok := dataBaseCache.get(name)
|
|
||||||
if ok {
|
|
||||||
return al.DB.DB, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stmtDecorator struct {
|
|
||||||
wg sync.WaitGroup
|
|
||||||
stmt *sql.Stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stmtDecorator) getStmt() *sql.Stmt {
|
|
||||||
return s.stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
// acquire will add one
|
|
||||||
// since this method will be used inside read lock scope,
|
|
||||||
// so we can not do more things here
|
|
||||||
// we should think about refactor this
|
|
||||||
func (s *stmtDecorator) acquire() {
|
|
||||||
s.wg.Add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stmtDecorator) release() {
|
|
||||||
s.wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
// garbage recycle for stmt
|
|
||||||
func (s *stmtDecorator) destroy() {
|
|
||||||
go func() {
|
|
||||||
s.wg.Wait()
|
|
||||||
_ = s.stmt.Close()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator {
|
|
||||||
return &stmtDecorator{
|
|
||||||
stmt: sqlStmt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStmtDecoratorLruWithEvict(cacheSize int) (*lru.Cache, error) {
|
|
||||||
cache, err := lru.NewWithEvict(cacheSize, func(key interface{}, value interface{}) {
|
|
||||||
value.(*stmtDecorator).destroy()
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DBOption func(al *alias)
|
|
||||||
|
|
||||||
// MaxIdleConnections return a hint about MaxIdleConnections
|
|
||||||
func MaxIdleConnections(maxIdleConn int) DBOption {
|
|
||||||
return func(al *alias) {
|
|
||||||
al.SetMaxIdleConns(maxIdleConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxOpenConnections return a hint about MaxOpenConnections
|
|
||||||
func MaxOpenConnections(maxOpenConn int) DBOption {
|
|
||||||
return func(al *alias) {
|
|
||||||
al.SetMaxOpenConns(maxOpenConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnMaxLifetime return a hint about ConnMaxLifetime
|
|
||||||
func ConnMaxLifetime(v time.Duration) DBOption {
|
|
||||||
return func(al *alias) {
|
|
||||||
al.SetConnMaxLifetime(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxStmtCacheSize return a hint about MaxStmtCacheSize
|
|
||||||
func MaxStmtCacheSize(v int) DBOption {
|
|
||||||
return func(al *alias) {
|
|
||||||
al.StmtCacheSize = v
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,192 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mysql operators.
|
|
||||||
var mysqlOperators = map[string]string{
|
|
||||||
"exact": "= ?",
|
|
||||||
"iexact": "LIKE ?",
|
|
||||||
"strictexact": "= BINARY ?",
|
|
||||||
"contains": "LIKE BINARY ?",
|
|
||||||
"icontains": "LIKE ?",
|
|
||||||
// "regex": "REGEXP BINARY ?",
|
|
||||||
// "iregex": "REGEXP ?",
|
|
||||||
"gt": "> ?",
|
|
||||||
"gte": ">= ?",
|
|
||||||
"lt": "< ?",
|
|
||||||
"lte": "<= ?",
|
|
||||||
"eq": "= ?",
|
|
||||||
"ne": "!= ?",
|
|
||||||
"startswith": "LIKE BINARY ?",
|
|
||||||
"endswith": "LIKE BINARY ?",
|
|
||||||
"istartswith": "LIKE ?",
|
|
||||||
"iendswith": "LIKE ?",
|
|
||||||
}
|
|
||||||
|
|
||||||
// mysql column field types.
|
|
||||||
var mysqlTypes = map[string]string{
|
|
||||||
"auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY",
|
|
||||||
"pk": "NOT NULL PRIMARY KEY",
|
|
||||||
"bool": "bool",
|
|
||||||
"string": "varchar(%d)",
|
|
||||||
"string-char": "char(%d)",
|
|
||||||
"string-text": "longtext",
|
|
||||||
"time.Time-date": "date",
|
|
||||||
"time.Time": "datetime",
|
|
||||||
"int8": "tinyint",
|
|
||||||
"int16": "smallint",
|
|
||||||
"int32": "integer",
|
|
||||||
"int64": "bigint",
|
|
||||||
"uint8": "tinyint unsigned",
|
|
||||||
"uint16": "smallint unsigned",
|
|
||||||
"uint32": "integer unsigned",
|
|
||||||
"uint64": "bigint unsigned",
|
|
||||||
"float64": "double precision",
|
|
||||||
"float64-decimal": "numeric(%d, %d)",
|
|
||||||
"time.Time-precision": "datetime(%d)",
|
|
||||||
}
|
|
||||||
|
|
||||||
// mysql dbBaser implementation.
|
|
||||||
type dbBaseMysql struct {
|
|
||||||
dbBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dbBaser = new(dbBaseMysql)
|
|
||||||
|
|
||||||
// get mysql operator.
|
|
||||||
func (d *dbBaseMysql) OperatorSQL(operator string) string {
|
|
||||||
return mysqlOperators[operator]
|
|
||||||
}
|
|
||||||
|
|
||||||
// get mysql table field types.
|
|
||||||
func (d *dbBaseMysql) DbTypes() map[string]string {
|
|
||||||
return mysqlTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// show table sql for mysql.
|
|
||||||
func (d *dbBaseMysql) ShowTablesQuery() string {
|
|
||||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
|
||||||
}
|
|
||||||
|
|
||||||
// show columns sql of table for mysql.
|
|
||||||
func (d *dbBaseMysql) ShowColumnsQuery(table string) string {
|
|
||||||
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
|
||||||
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute sql to check index exist.
|
|
||||||
func (d *dbBaseMysql) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
|
||||||
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
|
|
||||||
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
|
||||||
var cnt int
|
|
||||||
row.Scan(&cnt)
|
|
||||||
return cnt > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertOrUpdate a row
|
|
||||||
// If your primary key or unique column conflict will update
|
|
||||||
// If no will insert
|
|
||||||
// Add "`" for mysql sql building
|
|
||||||
func (d *dbBaseMysql) InsertOrUpdate(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
|
|
||||||
var iouStr string
|
|
||||||
argsMap := map[string]string{}
|
|
||||||
|
|
||||||
iouStr = "ON DUPLICATE KEY UPDATE"
|
|
||||||
|
|
||||||
// Get on the key-value pairs
|
|
||||||
for _, v := range args {
|
|
||||||
kv := strings.Split(v, "=")
|
|
||||||
if len(kv) == 2 {
|
|
||||||
argsMap[strings.ToLower(kv[0])] = kv[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isMulti := false
|
|
||||||
names := make([]string, 0, len(mi.fields.dbcols)-1)
|
|
||||||
Q := d.ins.TableQuote()
|
|
||||||
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
marks := make([]string, len(names))
|
|
||||||
updateValues := make([]interface{}, 0)
|
|
||||||
updates := make([]string, len(names))
|
|
||||||
|
|
||||||
for i, v := range names {
|
|
||||||
marks[i] = "?"
|
|
||||||
valueStr := argsMap[strings.ToLower(v)]
|
|
||||||
if valueStr != "" {
|
|
||||||
updates[i] = "`" + v + "`" + "=" + valueStr
|
|
||||||
} else {
|
|
||||||
updates[i] = "`" + v + "`" + "=?"
|
|
||||||
updateValues = append(updateValues, values[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(values, updateValues...)
|
|
||||||
|
|
||||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
|
||||||
qmarks := strings.Join(marks, ", ")
|
|
||||||
qupdates := strings.Join(updates, ", ")
|
|
||||||
columns := strings.Join(names, sep)
|
|
||||||
|
|
||||||
multi := len(values) / len(names)
|
|
||||||
|
|
||||||
if isMulti {
|
|
||||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
|
||||||
}
|
|
||||||
// conflitValue maybe is a int,can`t use fmt.Sprintf
|
|
||||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
|
||||||
|
|
||||||
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
|
||||||
res, err := q.ExecContext(ctx, query, values...)
|
|
||||||
if err == nil {
|
|
||||||
if isMulti {
|
|
||||||
return res.RowsAffected()
|
|
||||||
}
|
|
||||||
|
|
||||||
lastInsertId, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
DebugLog.Println(ErrLastInsertIdUnavailable, ':', err)
|
|
||||||
return lastInsertId, ErrLastInsertIdUnavailable
|
|
||||||
} else {
|
|
||||||
return lastInsertId, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
row := q.QueryRowContext(ctx, query, values...)
|
|
||||||
var id int64
|
|
||||||
err = row.Scan(&id)
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new mysql dbBaser.
|
|
||||||
func newdbBaseMysql() dbBaser {
|
|
||||||
b := new(dbBaseMysql)
|
|
||||||
b.ins = b
|
|
||||||
return b
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm/hints"
|
|
||||||
)
|
|
||||||
|
|
||||||
// oracle operators.
|
|
||||||
var oracleOperators = map[string]string{
|
|
||||||
"exact": "= ?",
|
|
||||||
"gt": "> ?",
|
|
||||||
"gte": ">= ?",
|
|
||||||
"lt": "< ?",
|
|
||||||
"lte": "<= ?",
|
|
||||||
"//iendswith": "LIKE ?",
|
|
||||||
}
|
|
||||||
|
|
||||||
// oracle column field types.
|
|
||||||
var oracleTypes = map[string]string{
|
|
||||||
"pk": "NOT NULL PRIMARY KEY",
|
|
||||||
"bool": "bool",
|
|
||||||
"string": "VARCHAR2(%d)",
|
|
||||||
"string-char": "CHAR(%d)",
|
|
||||||
"string-text": "VARCHAR2(%d)",
|
|
||||||
"time.Time-date": "DATE",
|
|
||||||
"time.Time": "TIMESTAMP",
|
|
||||||
"int8": "INTEGER",
|
|
||||||
"int16": "INTEGER",
|
|
||||||
"int32": "INTEGER",
|
|
||||||
"int64": "INTEGER",
|
|
||||||
"uint8": "INTEGER",
|
|
||||||
"uint16": "INTEGER",
|
|
||||||
"uint32": "INTEGER",
|
|
||||||
"uint64": "INTEGER",
|
|
||||||
"float64": "NUMBER",
|
|
||||||
"float64-decimal": "NUMBER(%d, %d)",
|
|
||||||
"time.Time-precision": "TIMESTAMP(%d)",
|
|
||||||
}
|
|
||||||
|
|
||||||
// oracle dbBaser
|
|
||||||
type dbBaseOracle struct {
|
|
||||||
dbBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dbBaser = new(dbBaseOracle)
|
|
||||||
|
|
||||||
// create oracle dbBaser.
|
|
||||||
func newdbBaseOracle() dbBaser {
|
|
||||||
b := new(dbBaseOracle)
|
|
||||||
b.ins = b
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// OperatorSQL get oracle operator.
|
|
||||||
func (d *dbBaseOracle) OperatorSQL(operator string) string {
|
|
||||||
return oracleOperators[operator]
|
|
||||||
}
|
|
||||||
|
|
||||||
// DbTypes get oracle table field types.
|
|
||||||
func (d *dbBaseOracle) DbTypes() map[string]string {
|
|
||||||
return oracleTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowTablesQuery show all the tables in database
|
|
||||||
func (d *dbBaseOracle) ShowTablesQuery() string {
|
|
||||||
return "SELECT TABLE_NAME FROM USER_TABLES"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Oracle
|
|
||||||
func (d *dbBaseOracle) ShowColumnsQuery(table string) string {
|
|
||||||
return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+
|
|
||||||
"WHERE TABLE_NAME ='%s'", strings.ToUpper(table))
|
|
||||||
}
|
|
||||||
|
|
||||||
// check index is exist
|
|
||||||
func (d *dbBaseOracle) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
|
||||||
row := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
|
|
||||||
"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+
|
|
||||||
"AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name))
|
|
||||||
|
|
||||||
var cnt int
|
|
||||||
row.Scan(&cnt)
|
|
||||||
return cnt > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dbBaseOracle) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
|
||||||
var s []string
|
|
||||||
Q := d.TableQuote()
|
|
||||||
for _, index := range indexes {
|
|
||||||
tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q)
|
|
||||||
s = append(s, tmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
var hint string
|
|
||||||
|
|
||||||
switch useIndex {
|
|
||||||
case hints.KeyUseIndex, hints.KeyForceIndex:
|
|
||||||
hint = `INDEX`
|
|
||||||
case hints.KeyIgnoreIndex:
|
|
||||||
hint = `NO_INDEX`
|
|
||||||
default:
|
|
||||||
DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored")
|
|
||||||
return ``
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(` /*+ %s(%s %s)*/ `, hint, tableName, strings.Join(s, `,`))
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute insert sql with given struct and given values.
|
|
||||||
// insert the given values, not the field values in struct.
|
|
||||||
func (d *dbBaseOracle) InsertValue(ctx context.Context, q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
|
|
||||||
Q := d.ins.TableQuote()
|
|
||||||
|
|
||||||
marks := make([]string, len(names))
|
|
||||||
for i := range marks {
|
|
||||||
marks[i] = ":" + names[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
|
||||||
qmarks := strings.Join(marks, ", ")
|
|
||||||
columns := strings.Join(names, sep)
|
|
||||||
|
|
||||||
multi := len(values) / len(names)
|
|
||||||
|
|
||||||
if isMulti {
|
|
||||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s)", Q, mi.table, Q, Q, columns, Q, qmarks)
|
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
|
||||||
|
|
||||||
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
|
||||||
res, err := q.ExecContext(ctx, query, values...)
|
|
||||||
if err == nil {
|
|
||||||
if isMulti {
|
|
||||||
return res.RowsAffected()
|
|
||||||
}
|
|
||||||
|
|
||||||
lastInsertId, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
DebugLog.Println(ErrLastInsertIdUnavailable, ':', err)
|
|
||||||
return lastInsertId, ErrLastInsertIdUnavailable
|
|
||||||
} else {
|
|
||||||
return lastInsertId, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
row := q.QueryRowContext(ctx, query, values...)
|
|
||||||
var id int64
|
|
||||||
err := row.Scan(&id)
|
|
||||||
return id, err
|
|
||||||
}
|
|
@ -1,197 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// postgresql operators.
|
|
||||||
var postgresOperators = map[string]string{
|
|
||||||
"exact": "= ?",
|
|
||||||
"iexact": "= UPPER(?)",
|
|
||||||
"contains": "LIKE ?",
|
|
||||||
"icontains": "LIKE UPPER(?)",
|
|
||||||
"gt": "> ?",
|
|
||||||
"gte": ">= ?",
|
|
||||||
"lt": "< ?",
|
|
||||||
"lte": "<= ?",
|
|
||||||
"eq": "= ?",
|
|
||||||
"ne": "!= ?",
|
|
||||||
"startswith": "LIKE ?",
|
|
||||||
"endswith": "LIKE ?",
|
|
||||||
"istartswith": "LIKE UPPER(?)",
|
|
||||||
"iendswith": "LIKE UPPER(?)",
|
|
||||||
}
|
|
||||||
|
|
||||||
// postgresql column field types.
|
|
||||||
var postgresTypes = map[string]string{
|
|
||||||
"auto": "serial NOT NULL PRIMARY KEY",
|
|
||||||
"pk": "NOT NULL PRIMARY KEY",
|
|
||||||
"bool": "bool",
|
|
||||||
"string": "varchar(%d)",
|
|
||||||
"string-char": "char(%d)",
|
|
||||||
"string-text": "text",
|
|
||||||
"time.Time-date": "date",
|
|
||||||
"time.Time": "timestamp with time zone",
|
|
||||||
"int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`,
|
|
||||||
"int16": "smallint",
|
|
||||||
"int32": "integer",
|
|
||||||
"int64": "bigint",
|
|
||||||
"uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`,
|
|
||||||
"uint16": `integer CHECK("%COL%" >= 0)`,
|
|
||||||
"uint32": `bigint CHECK("%COL%" >= 0)`,
|
|
||||||
"uint64": `bigint CHECK("%COL%" >= 0)`,
|
|
||||||
"float64": "double precision",
|
|
||||||
"float64-decimal": "numeric(%d, %d)",
|
|
||||||
"json": "json",
|
|
||||||
"jsonb": "jsonb",
|
|
||||||
"time.Time-precision": "timestamp(%d) with time zone",
|
|
||||||
}
|
|
||||||
|
|
||||||
// postgresql dbBaser.
|
|
||||||
type dbBasePostgres struct {
|
|
||||||
dbBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dbBaser = new(dbBasePostgres)
|
|
||||||
|
|
||||||
// get postgresql operator.
|
|
||||||
func (d *dbBasePostgres) OperatorSQL(operator string) string {
|
|
||||||
return postgresOperators[operator]
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate functioned sql string, such as contains(text).
|
|
||||||
func (d *dbBasePostgres) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
|
||||||
switch operator {
|
|
||||||
case "contains", "startswith", "endswith":
|
|
||||||
*leftCol = fmt.Sprintf("%s::text", *leftCol)
|
|
||||||
case "iexact", "icontains", "istartswith", "iendswith":
|
|
||||||
*leftCol = fmt.Sprintf("UPPER(%s::text)", *leftCol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// postgresql unsupports updating joined record.
|
|
||||||
func (d *dbBasePostgres) SupportUpdateJoin() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dbBasePostgres) MaxLimit() uint64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// postgresql quote is ".
|
|
||||||
func (d *dbBasePostgres) TableQuote() string {
|
|
||||||
return `"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// postgresql value placeholder is $n.
|
|
||||||
// replace default ? to $n.
|
|
||||||
func (d *dbBasePostgres) ReplaceMarks(query *string) {
|
|
||||||
q := *query
|
|
||||||
num := 0
|
|
||||||
for _, c := range q {
|
|
||||||
if c == '?' {
|
|
||||||
num++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if num == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data := make([]byte, 0, len(q)+num)
|
|
||||||
num = 1
|
|
||||||
for i := 0; i < len(q); i++ {
|
|
||||||
c := q[i]
|
|
||||||
if c == '?' {
|
|
||||||
data = append(data, '$')
|
|
||||||
data = append(data, []byte(strconv.Itoa(num))...)
|
|
||||||
num++
|
|
||||||
} else {
|
|
||||||
data = append(data, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*query = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make returning sql support for postgresql.
|
|
||||||
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) bool {
|
|
||||||
fi := mi.fields.pk
|
|
||||||
if fi.fieldType&IsPositiveIntegerField == 0 && fi.fieldType&IsIntegerField == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if query != nil {
|
|
||||||
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, fi.column)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync auto key
|
|
||||||
func (d *dbBasePostgres) setval(ctx context.Context, db dbQuerier, mi *modelInfo, autoFields []string) error {
|
|
||||||
if len(autoFields) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
Q := d.ins.TableQuote()
|
|
||||||
for _, name := range autoFields {
|
|
||||||
query := fmt.Sprintf("SELECT setval(pg_get_serial_sequence('%s', '%s'), (SELECT MAX(%s%s%s) FROM %s%s%s));",
|
|
||||||
mi.table, name,
|
|
||||||
Q, name, Q,
|
|
||||||
Q, mi.table, Q)
|
|
||||||
if _, err := db.ExecContext(ctx, query); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// show table sql for postgresql.
|
|
||||||
func (d *dbBasePostgres) ShowTablesQuery() string {
|
|
||||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema')"
|
|
||||||
}
|
|
||||||
|
|
||||||
// show table columns sql for postgresql.
|
|
||||||
func (d *dbBasePostgres) ShowColumnsQuery(table string) string {
|
|
||||||
return fmt.Sprintf("SELECT column_name, data_type, is_nullable FROM information_schema.columns where table_schema NOT IN ('pg_catalog', 'information_schema') and table_name = '%s'", table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get column types of postgresql.
|
|
||||||
func (d *dbBasePostgres) DbTypes() map[string]string {
|
|
||||||
return postgresTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// check index exist in postgresql.
|
|
||||||
func (d *dbBasePostgres) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
|
||||||
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_indexes WHERE tablename = '%s' AND indexname = '%s'", table, name)
|
|
||||||
row := db.QueryRowContext(ctx, query)
|
|
||||||
var cnt int
|
|
||||||
row.Scan(&cnt)
|
|
||||||
return cnt > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSpecifyIndex return a specifying index clause
|
|
||||||
func (d *dbBasePostgres) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
|
||||||
DebugLog.Println("[WARN] Not support any specifying index action, so that action is ignored")
|
|
||||||
return ``
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new postgresql dbBaser.
|
|
||||||
func newdbBasePostgres() dbBaser {
|
|
||||||
b := new(dbBasePostgres)
|
|
||||||
b.ins = b
|
|
||||||
return b
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm/hints"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sqlite operators.
|
|
||||||
var sqliteOperators = map[string]string{
|
|
||||||
"exact": "= ?",
|
|
||||||
"iexact": "LIKE ? ESCAPE '\\'",
|
|
||||||
"contains": "LIKE ? ESCAPE '\\'",
|
|
||||||
"icontains": "LIKE ? ESCAPE '\\'",
|
|
||||||
"gt": "> ?",
|
|
||||||
"gte": ">= ?",
|
|
||||||
"lt": "< ?",
|
|
||||||
"lte": "<= ?",
|
|
||||||
"eq": "= ?",
|
|
||||||
"ne": "!= ?",
|
|
||||||
"startswith": "LIKE ? ESCAPE '\\'",
|
|
||||||
"endswith": "LIKE ? ESCAPE '\\'",
|
|
||||||
"istartswith": "LIKE ? ESCAPE '\\'",
|
|
||||||
"iendswith": "LIKE ? ESCAPE '\\'",
|
|
||||||
}
|
|
||||||
|
|
||||||
// sqlite column types.
|
|
||||||
var sqliteTypes = map[string]string{
|
|
||||||
"auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT",
|
|
||||||
"pk": "NOT NULL PRIMARY KEY",
|
|
||||||
"bool": "bool",
|
|
||||||
"string": "varchar(%d)",
|
|
||||||
"string-char": "character(%d)",
|
|
||||||
"string-text": "text",
|
|
||||||
"time.Time-date": "date",
|
|
||||||
"time.Time": "datetime",
|
|
||||||
"time.Time-precision": "datetime(%d)",
|
|
||||||
"int8": "tinyint",
|
|
||||||
"int16": "smallint",
|
|
||||||
"int32": "integer",
|
|
||||||
"int64": "bigint",
|
|
||||||
"uint8": "tinyint unsigned",
|
|
||||||
"uint16": "smallint unsigned",
|
|
||||||
"uint32": "integer unsigned",
|
|
||||||
"uint64": "bigint unsigned",
|
|
||||||
"float64": "real",
|
|
||||||
"float64-decimal": "decimal",
|
|
||||||
}
|
|
||||||
|
|
||||||
// sqlite dbBaser.
|
|
||||||
type dbBaseSqlite struct {
|
|
||||||
dbBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dbBaser = new(dbBaseSqlite)
|
|
||||||
|
|
||||||
// override base db read for update behavior as SQlite does not support syntax
|
|
||||||
func (d *dbBaseSqlite) Read(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
|
|
||||||
if isForUpdate {
|
|
||||||
DebugLog.Println("[WARN] SQLite does not support SELECT FOR UPDATE query, isForUpdate param is ignored and always as false to do the work")
|
|
||||||
}
|
|
||||||
return d.dbBase.Read(ctx, q, mi, ind, tz, cols, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get sqlite operator.
|
|
||||||
func (d *dbBaseSqlite) OperatorSQL(operator string) string {
|
|
||||||
return sqliteOperators[operator]
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate functioned sql for sqlite.
|
|
||||||
// only support DATE(text).
|
|
||||||
func (d *dbBaseSqlite) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
|
||||||
if fi.fieldType == TypeDateField {
|
|
||||||
*leftCol = fmt.Sprintf("DATE(%s)", *leftCol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unable updating joined record in sqlite.
|
|
||||||
func (d *dbBaseSqlite) SupportUpdateJoin() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// max int in sqlite.
|
|
||||||
func (d *dbBaseSqlite) MaxLimit() uint64 {
|
|
||||||
return 9223372036854775807
|
|
||||||
}
|
|
||||||
|
|
||||||
// get column types in sqlite.
|
|
||||||
func (d *dbBaseSqlite) DbTypes() map[string]string {
|
|
||||||
return sqliteTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// get show tables sql in sqlite.
|
|
||||||
func (d *dbBaseSqlite) ShowTablesQuery() string {
|
|
||||||
return "SELECT name FROM sqlite_master WHERE type = 'table'"
|
|
||||||
}
|
|
||||||
|
|
||||||
// get columns in sqlite.
|
|
||||||
func (d *dbBaseSqlite) GetColumns(ctx context.Context, db dbQuerier, table string) (map[string][3]string, error) {
|
|
||||||
query := d.ins.ShowColumnsQuery(table)
|
|
||||||
rows, err := db.QueryContext(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
columns := make(map[string][3]string)
|
|
||||||
for rows.Next() {
|
|
||||||
var tmp, name, typ, null sql.NullString
|
|
||||||
err := rows.Scan(&tmp, &name, &typ, &null, &tmp, &tmp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
columns[name.String] = [3]string{name.String, typ.String, null.String}
|
|
||||||
}
|
|
||||||
|
|
||||||
return columns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get show columns sql in sqlite.
|
|
||||||
func (d *dbBaseSqlite) ShowColumnsQuery(table string) string {
|
|
||||||
return fmt.Sprintf("pragma table_info('%s')", table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check index exist in sqlite.
|
|
||||||
func (d *dbBaseSqlite) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
|
||||||
query := fmt.Sprintf("PRAGMA index_list('%s')", table)
|
|
||||||
rows, err := db.QueryContext(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var tmp, index sql.NullString
|
|
||||||
rows.Scan(&tmp, &index, &tmp, &tmp, &tmp)
|
|
||||||
if name == index.String {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSpecifyIndex return a specifying index clause
|
|
||||||
func (d *dbBaseSqlite) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
|
||||||
var s []string
|
|
||||||
Q := d.TableQuote()
|
|
||||||
for _, index := range indexes {
|
|
||||||
tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q)
|
|
||||||
s = append(s, tmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch useIndex {
|
|
||||||
case hints.KeyUseIndex, hints.KeyForceIndex:
|
|
||||||
return fmt.Sprintf(` INDEXED BY %s `, strings.Join(s, `,`))
|
|
||||||
default:
|
|
||||||
DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored")
|
|
||||||
return ``
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new sqlite dbBaser.
|
|
||||||
func newdbBaseSqlite() dbBaser {
|
|
||||||
b := new(dbBaseSqlite)
|
|
||||||
b.ins = b
|
|
||||||
return b
|
|
||||||
}
|
|
@ -1,499 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm/clauses"
|
|
||||||
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
// table info struct.
|
|
||||||
type dbTable struct {
|
|
||||||
id int
|
|
||||||
index string
|
|
||||||
name string
|
|
||||||
names []string
|
|
||||||
sel bool
|
|
||||||
inner bool
|
|
||||||
mi *modelInfo
|
|
||||||
fi *fieldInfo
|
|
||||||
jtl *dbTable
|
|
||||||
}
|
|
||||||
|
|
||||||
// tables collection struct, contains some tables.
|
|
||||||
type dbTables struct {
|
|
||||||
tablesM map[string]*dbTable
|
|
||||||
tables []*dbTable
|
|
||||||
mi *modelInfo
|
|
||||||
base dbBaser
|
|
||||||
skipEnd bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// set table info to collection.
|
|
||||||
// if not exist, create new.
|
|
||||||
func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool) *dbTable {
|
|
||||||
name := strings.Join(names, ExprSep)
|
|
||||||
if j, ok := t.tablesM[name]; ok {
|
|
||||||
j.name = name
|
|
||||||
j.mi = mi
|
|
||||||
j.fi = fi
|
|
||||||
j.inner = inner
|
|
||||||
} else {
|
|
||||||
i := len(t.tables) + 1
|
|
||||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
|
||||||
t.tablesM[name] = jt
|
|
||||||
t.tables = append(t.tables, jt)
|
|
||||||
}
|
|
||||||
return t.tablesM[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// add table info to collection.
|
|
||||||
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
|
|
||||||
name := strings.Join(names, ExprSep)
|
|
||||||
if _, ok := t.tablesM[name]; !ok {
|
|
||||||
i := len(t.tables) + 1
|
|
||||||
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
|
||||||
t.tablesM[name] = jt
|
|
||||||
t.tables = append(t.tables, jt)
|
|
||||||
return jt, true
|
|
||||||
}
|
|
||||||
return t.tablesM[name], false
|
|
||||||
}
|
|
||||||
|
|
||||||
// get table info in collection.
|
|
||||||
func (t *dbTables) get(name string) (*dbTable, bool) {
|
|
||||||
j, ok := t.tablesM[name]
|
|
||||||
return j, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// get related fields info in recursive depth loop.
|
|
||||||
// loop once, depth decreases one.
|
|
||||||
func (t *dbTables) loopDepth(depth int, prefix string, fi *fieldInfo, related []string) []string {
|
|
||||||
if depth < 0 || fi.fieldType == RelManyToMany {
|
|
||||||
return related
|
|
||||||
}
|
|
||||||
|
|
||||||
if prefix == "" {
|
|
||||||
prefix = fi.name
|
|
||||||
} else {
|
|
||||||
prefix = prefix + ExprSep + fi.name
|
|
||||||
}
|
|
||||||
related = append(related, prefix)
|
|
||||||
|
|
||||||
depth--
|
|
||||||
for _, fi := range fi.relModelInfo.fields.fieldsRel {
|
|
||||||
related = t.loopDepth(depth, prefix, fi, related)
|
|
||||||
}
|
|
||||||
|
|
||||||
return related
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse related fields.
|
|
||||||
func (t *dbTables) parseRelated(rels []string, depth int) {
|
|
||||||
relsNum := len(rels)
|
|
||||||
related := make([]string, relsNum)
|
|
||||||
copy(related, rels)
|
|
||||||
|
|
||||||
relDepth := depth
|
|
||||||
|
|
||||||
if relsNum != 0 {
|
|
||||||
relDepth = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
relDepth--
|
|
||||||
for _, fi := range t.mi.fields.fieldsRel {
|
|
||||||
related = t.loopDepth(relDepth, "", fi, related)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range related {
|
|
||||||
var (
|
|
||||||
exs = strings.Split(s, ExprSep)
|
|
||||||
names = make([]string, 0, len(exs))
|
|
||||||
mmi = t.mi
|
|
||||||
cancel = true
|
|
||||||
jtl *dbTable
|
|
||||||
)
|
|
||||||
|
|
||||||
inner := true
|
|
||||||
|
|
||||||
for _, ex := range exs {
|
|
||||||
if fi, ok := mmi.fields.GetByAny(ex); ok && fi.rel && fi.fieldType != RelManyToMany {
|
|
||||||
names = append(names, fi.name)
|
|
||||||
mmi = fi.relModelInfo
|
|
||||||
|
|
||||||
if fi.null || t.skipEnd {
|
|
||||||
inner = false
|
|
||||||
}
|
|
||||||
|
|
||||||
jt := t.set(names, mmi, fi, inner)
|
|
||||||
jt.jtl = jtl
|
|
||||||
|
|
||||||
if fi.reverse {
|
|
||||||
cancel = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if cancel {
|
|
||||||
jt.sel = depth > 0
|
|
||||||
|
|
||||||
if i < relsNum {
|
|
||||||
jt.sel = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jtl = jt
|
|
||||||
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("unknown model/table name `%s`", ex))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate join string.
|
|
||||||
func (t *dbTables) getJoinSQL() (join string) {
|
|
||||||
Q := t.base.TableQuote()
|
|
||||||
|
|
||||||
for _, jt := range t.tables {
|
|
||||||
if jt.inner {
|
|
||||||
join += "INNER JOIN "
|
|
||||||
} else {
|
|
||||||
join += "LEFT OUTER JOIN "
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
table string
|
|
||||||
t1, t2 string
|
|
||||||
c1, c2 string
|
|
||||||
)
|
|
||||||
t1 = "T0"
|
|
||||||
if jt.jtl != nil {
|
|
||||||
t1 = jt.jtl.index
|
|
||||||
}
|
|
||||||
t2 = jt.index
|
|
||||||
table = jt.mi.table
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case jt.fi.fieldType == RelManyToMany || jt.fi.fieldType == RelReverseMany || jt.fi.reverse && jt.fi.reverseFieldInfo.fieldType == RelManyToMany:
|
|
||||||
c1 = jt.fi.mi.fields.pk.column
|
|
||||||
for _, ffi := range jt.mi.fields.fieldsRel {
|
|
||||||
if jt.fi.mi == ffi.relModelInfo {
|
|
||||||
c2 = ffi.column
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
c1 = jt.fi.column
|
|
||||||
c2 = jt.fi.relModelInfo.fields.pk.column
|
|
||||||
|
|
||||||
if jt.fi.reverse {
|
|
||||||
c1 = jt.mi.fields.pk.column
|
|
||||||
c2 = jt.fi.reverseFieldInfo.column
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
join += fmt.Sprintf("%s%s%s %s ON %s.%s%s%s = %s.%s%s%s ", Q, table, Q, t2,
|
|
||||||
t2, Q, c2, Q, t1, Q, c1, Q)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse orm model struct field tag expression.
|
|
||||||
func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string, info *fieldInfo, success bool) {
|
|
||||||
var (
|
|
||||||
jtl *dbTable
|
|
||||||
fi *fieldInfo
|
|
||||||
fiN *fieldInfo
|
|
||||||
mmi = mi
|
|
||||||
)
|
|
||||||
|
|
||||||
num := len(exprs) - 1
|
|
||||||
var names []string
|
|
||||||
|
|
||||||
inner := true
|
|
||||||
|
|
||||||
loopFor:
|
|
||||||
for i, ex := range exprs {
|
|
||||||
|
|
||||||
var ok, okN bool
|
|
||||||
|
|
||||||
if fiN != nil {
|
|
||||||
fi = fiN
|
|
||||||
ok = true
|
|
||||||
fiN = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
fi, ok = mmi.fields.GetByAny(ex)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = okN
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
|
|
||||||
isRel := fi.rel || fi.reverse
|
|
||||||
|
|
||||||
names = append(names, fi.name)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case fi.rel:
|
|
||||||
mmi = fi.relModelInfo
|
|
||||||
if fi.fieldType == RelManyToMany {
|
|
||||||
mmi = fi.relThroughModelInfo
|
|
||||||
}
|
|
||||||
case fi.reverse:
|
|
||||||
mmi = fi.reverseFieldInfo.mi
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < num {
|
|
||||||
fiN, okN = mmi.fields.GetByAny(exprs[i+1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if isRel && (!fi.mi.isThrough || num != i) {
|
|
||||||
if fi.null || t.skipEnd {
|
|
||||||
inner = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.skipEnd && okN || !t.skipEnd {
|
|
||||||
if t.skipEnd && okN && fiN.pk {
|
|
||||||
goto loopEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
jt, _ := t.add(names, mmi, fi, inner)
|
|
||||||
jt.jtl = jtl
|
|
||||||
jtl = jt
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if num != i {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
loopEnd:
|
|
||||||
|
|
||||||
if i == 0 || jtl == nil {
|
|
||||||
index = "T0"
|
|
||||||
} else {
|
|
||||||
index = jtl.index
|
|
||||||
}
|
|
||||||
|
|
||||||
info = fi
|
|
||||||
|
|
||||||
if jtl == nil {
|
|
||||||
name = fi.name
|
|
||||||
} else {
|
|
||||||
name = jtl.name + ExprSep + fi.name
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case fi.rel:
|
|
||||||
|
|
||||||
case fi.reverse:
|
|
||||||
switch fi.reverseFieldInfo.fieldType {
|
|
||||||
case RelOneToOne, RelForeignKey:
|
|
||||||
index = jtl.index
|
|
||||||
info = fi.reverseFieldInfo.mi.fields.pk
|
|
||||||
name = info.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break loopFor
|
|
||||||
|
|
||||||
} else {
|
|
||||||
index = ""
|
|
||||||
name = ""
|
|
||||||
info = nil
|
|
||||||
success = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
success = index != "" && info != nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate condition sql.
|
|
||||||
func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (where string, params []interface{}) {
|
|
||||||
if cond == nil || cond.IsEmpty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Q := t.base.TableQuote()
|
|
||||||
|
|
||||||
mi := t.mi
|
|
||||||
|
|
||||||
for i, p := range cond.params {
|
|
||||||
if i > 0 {
|
|
||||||
if p.isOr {
|
|
||||||
where += "OR "
|
|
||||||
} else {
|
|
||||||
where += "AND "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.isNot {
|
|
||||||
where += "NOT "
|
|
||||||
}
|
|
||||||
if p.isCond {
|
|
||||||
w, ps := t.getCondSQL(p.cond, true, tz)
|
|
||||||
if w != "" {
|
|
||||||
w = fmt.Sprintf("( %s) ", w)
|
|
||||||
}
|
|
||||||
where += w
|
|
||||||
params = append(params, ps...)
|
|
||||||
} else {
|
|
||||||
exprs := p.exprs
|
|
||||||
|
|
||||||
num := len(exprs) - 1
|
|
||||||
operator := ""
|
|
||||||
if operators[exprs[num]] {
|
|
||||||
operator = exprs[num]
|
|
||||||
exprs = exprs[:num]
|
|
||||||
}
|
|
||||||
|
|
||||||
index, _, fi, suc := t.parseExprs(mi, exprs)
|
|
||||||
if !suc {
|
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if operator == "" {
|
|
||||||
operator = "exact"
|
|
||||||
}
|
|
||||||
|
|
||||||
var operSQL string
|
|
||||||
var args []interface{}
|
|
||||||
if p.isRaw {
|
|
||||||
operSQL = p.sql
|
|
||||||
} else {
|
|
||||||
operSQL, args = t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz)
|
|
||||||
}
|
|
||||||
|
|
||||||
leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q)
|
|
||||||
t.base.GenerateOperatorLeftCol(fi, operator, &leftCol)
|
|
||||||
|
|
||||||
where += fmt.Sprintf("%s %s ", leftCol, operSQL)
|
|
||||||
params = append(params, args...)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sub && where != "" {
|
|
||||||
where = "WHERE " + where
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate group sql.
|
|
||||||
func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
|
|
||||||
if len(groups) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Q := t.base.TableQuote()
|
|
||||||
|
|
||||||
groupSqls := make([]string, 0, len(groups))
|
|
||||||
for _, group := range groups {
|
|
||||||
exprs := strings.Split(group, ExprSep)
|
|
||||||
|
|
||||||
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
|
||||||
if !suc {
|
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
|
||||||
}
|
|
||||||
|
|
||||||
groupSqls = append(groupSqls, fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q))
|
|
||||||
}
|
|
||||||
|
|
||||||
groupSQL = fmt.Sprintf("GROUP BY %s ", strings.Join(groupSqls, ", "))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate order sql.
|
|
||||||
func (t *dbTables) getOrderSQL(orders []*order_clause.Order) (orderSQL string) {
|
|
||||||
if len(orders) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Q := t.base.TableQuote()
|
|
||||||
|
|
||||||
orderSqls := make([]string, 0, len(orders))
|
|
||||||
for _, order := range orders {
|
|
||||||
column := order.GetColumn()
|
|
||||||
clause := strings.Split(column, clauses.ExprDot)
|
|
||||||
|
|
||||||
if order.IsRaw() {
|
|
||||||
if len(clause) == 2 {
|
|
||||||
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", clause[0], Q, clause[1], Q, order.SortString()))
|
|
||||||
} else if len(clause) == 1 {
|
|
||||||
orderSqls = append(orderSqls, fmt.Sprintf("%s%s%s %s", Q, clause[0], Q, order.SortString()))
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
index, _, fi, suc := t.parseExprs(t.mi, clause)
|
|
||||||
if !suc {
|
|
||||||
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
|
||||||
}
|
|
||||||
|
|
||||||
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, order.SortString()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
orderSQL = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate limit sql.
|
|
||||||
func (t *dbTables) getLimitSQL(mi *modelInfo, offset int64, limit int64) (limits string) {
|
|
||||||
if limit == 0 {
|
|
||||||
limit = int64(DefaultRowsLimit)
|
|
||||||
}
|
|
||||||
if limit < 0 {
|
|
||||||
// no limit
|
|
||||||
if offset > 0 {
|
|
||||||
maxLimit := t.base.MaxLimit()
|
|
||||||
if maxLimit == 0 {
|
|
||||||
limits = fmt.Sprintf("OFFSET %d", offset)
|
|
||||||
} else {
|
|
||||||
limits = fmt.Sprintf("LIMIT %d OFFSET %d", maxLimit, offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if offset <= 0 {
|
|
||||||
limits = fmt.Sprintf("LIMIT %d", limit)
|
|
||||||
} else {
|
|
||||||
limits = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getIndexSql generate index sql.
|
|
||||||
func (t *dbTables) getIndexSql(tableName string, useIndex int, indexes []string) (clause string) {
|
|
||||||
if len(indexes) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.base.GenerateSpecifyIndex(tableName, useIndex, indexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// crete new tables collection.
|
|
||||||
func newDbTables(mi *modelInfo, base dbBaser) *dbTables {
|
|
||||||
tables := &dbTables{}
|
|
||||||
tables.tablesM = make(map[string]*dbTable)
|
|
||||||
tables.mi = mi
|
|
||||||
tables.base = base
|
|
||||||
return tables
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
// Copyright 2015 TiDB Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mysql dbBaser implementation.
|
|
||||||
type dbBaseTidb struct {
|
|
||||||
dbBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dbBaser = new(dbBaseTidb)
|
|
||||||
|
|
||||||
// get mysql operator.
|
|
||||||
func (d *dbBaseTidb) OperatorSQL(operator string) string {
|
|
||||||
return mysqlOperators[operator]
|
|
||||||
}
|
|
||||||
|
|
||||||
// get mysql table field types.
|
|
||||||
func (d *dbBaseTidb) DbTypes() map[string]string {
|
|
||||||
return mysqlTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// show table sql for mysql.
|
|
||||||
func (d *dbBaseTidb) ShowTablesQuery() string {
|
|
||||||
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
|
||||||
}
|
|
||||||
|
|
||||||
// show columns sql of table for mysql.
|
|
||||||
func (d *dbBaseTidb) ShowColumnsQuery(table string) string {
|
|
||||||
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
|
||||||
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute sql to check index exist.
|
|
||||||
func (d *dbBaseTidb) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
|
||||||
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
|
|
||||||
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
|
||||||
var cnt int
|
|
||||||
row.Scan(&cnt)
|
|
||||||
return cnt > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new mysql dbBaser.
|
|
||||||
func newdbBaseTidb() dbBaser {
|
|
||||||
b := new(dbBaseTidb)
|
|
||||||
b.ins = b
|
|
||||||
return b
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// get table alias.
|
|
||||||
func getDbAlias(name string) *alias {
|
|
||||||
if al, ok := dataBaseCache.get(name); ok {
|
|
||||||
return al
|
|
||||||
}
|
|
||||||
panic(fmt.Errorf("unknown DataBase alias name %s", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// get pk column info.
|
|
||||||
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
|
|
||||||
fi := mi.fields.pk
|
|
||||||
|
|
||||||
v := ind.FieldByIndex(fi.fieldIndex)
|
|
||||||
if fi.fieldType&IsPositiveIntegerField > 0 {
|
|
||||||
vu := v.Uint()
|
|
||||||
exist = vu > 0
|
|
||||||
value = vu
|
|
||||||
} else if fi.fieldType&IsIntegerField > 0 {
|
|
||||||
vu := v.Int()
|
|
||||||
exist = true
|
|
||||||
value = vu
|
|
||||||
} else if fi.fieldType&IsRelField > 0 {
|
|
||||||
_, value, exist = getExistPk(fi.relModelInfo, reflect.Indirect(v))
|
|
||||||
} else {
|
|
||||||
vu := v.String()
|
|
||||||
exist = vu != ""
|
|
||||||
value = vu
|
|
||||||
}
|
|
||||||
|
|
||||||
column = fi.column
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get fields description as flatted string.
|
|
||||||
func getFlatParams(fi *fieldInfo, args []interface{}, tz *time.Location) (params []interface{}) {
|
|
||||||
outFor:
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == nil {
|
|
||||||
params = append(params, arg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.ValueOf(arg)
|
|
||||||
kind := val.Kind()
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
kind = val.Kind()
|
|
||||||
arg = val.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.String:
|
|
||||||
v := val.String()
|
|
||||||
if fi != nil {
|
|
||||||
if fi.fieldType == TypeTimeField || fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
|
|
||||||
var t time.Time
|
|
||||||
var err error
|
|
||||||
if len(v) >= 19 {
|
|
||||||
s := v[:19]
|
|
||||||
t, err = time.ParseInLocation(formatDateTime, s, DefaultTimeLoc)
|
|
||||||
} else if len(v) >= 10 {
|
|
||||||
s := v
|
|
||||||
if len(v) > 10 {
|
|
||||||
s = v[:10]
|
|
||||||
}
|
|
||||||
t, err = time.ParseInLocation(formatDate, s, tz)
|
|
||||||
} else {
|
|
||||||
s := v
|
|
||||||
if len(s) > 8 {
|
|
||||||
s = v[:8]
|
|
||||||
}
|
|
||||||
t, err = time.ParseInLocation(formatTime, s, tz)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
if fi.fieldType == TypeDateField {
|
|
||||||
v = t.In(tz).Format(formatDate)
|
|
||||||
} else if fi.fieldType == TypeDateTimeField {
|
|
||||||
v = t.In(tz).Format(formatDateTime)
|
|
||||||
} else {
|
|
||||||
v = t.In(tz).Format(formatTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arg = v
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
arg = val.Int()
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
arg = val.Uint()
|
|
||||||
case reflect.Float32:
|
|
||||||
arg, _ = StrTo(ToStr(arg)).Float64()
|
|
||||||
case reflect.Float64:
|
|
||||||
arg = val.Float()
|
|
||||||
case reflect.Bool:
|
|
||||||
arg = val.Bool()
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
if _, ok := arg.([]byte); ok {
|
|
||||||
continue outFor
|
|
||||||
}
|
|
||||||
|
|
||||||
var args []interface{}
|
|
||||||
for i := 0; i < val.Len(); i++ {
|
|
||||||
v := val.Index(i)
|
|
||||||
|
|
||||||
var vu interface{}
|
|
||||||
if v.CanInterface() {
|
|
||||||
vu = v.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
if vu == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, vu)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
p := getFlatParams(fi, args, tz)
|
|
||||||
params = append(params, p...)
|
|
||||||
}
|
|
||||||
continue outFor
|
|
||||||
case reflect.Struct:
|
|
||||||
if v, ok := arg.(time.Time); ok {
|
|
||||||
if fi != nil && fi.fieldType == TypeDateField {
|
|
||||||
arg = v.In(tz).Format(formatDate)
|
|
||||||
} else if fi != nil && fi.fieldType == TypeDateTimeField {
|
|
||||||
arg = v.In(tz).Format(formatDateTime)
|
|
||||||
} else if fi != nil && fi.fieldType == TypeTimeField {
|
|
||||||
arg = v.In(tz).Format(formatTime)
|
|
||||||
} else {
|
|
||||||
arg = v.In(tz).Format(formatDateTime)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
typ := val.Type()
|
|
||||||
name := getFullName(typ)
|
|
||||||
var value interface{}
|
|
||||||
if mmi, ok := defaultModelCache.getByFullName(name); ok {
|
|
||||||
if _, vu, exist := getExistPk(mmi, val); exist {
|
|
||||||
value = vu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arg = value
|
|
||||||
|
|
||||||
if arg == nil {
|
|
||||||
panic(fmt.Errorf("need a valid args value, unknown table or value `%s`", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params = append(params, arg)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
// Copyright 2020 beego
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/core/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation
|
|
||||||
// I think golang mocking interface is hard to use
|
|
||||||
// this may help you to integrate with Ormer
|
|
||||||
|
|
||||||
var _ Ormer = new(DoNothingOrm)
|
|
||||||
|
|
||||||
type DoNothingOrm struct{}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Read(md interface{}, cols ...string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) ReadForUpdate(md interface{}, cols ...string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
|
||||||
return false, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
|
||||||
return false, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) QueryM2M(md interface{}, name string) QueryM2Mer {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
|
||||||
func (d *DoNothingOrm) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
|
||||||
func (d *DoNothingOrm) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) DBStats() *sql.DBStats {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Insert(md interface{}) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Update(md interface{}, cols ...string) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Delete(md interface{}, cols ...string) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Raw(query string, args ...interface{}) RawSeter {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Driver() Driver {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) Begin() (TxOrmer, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoNothingTxOrm is similar with DoNothingOrm, usually you use it to test
|
|
||||||
type DoNothingTxOrm struct {
|
|
||||||
DoNothingOrm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingTxOrm) Commit() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoNothingTxOrm) Rollback() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
// Copyright 2020 beego
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterChain is used to build a Filter
|
|
||||||
// don't forget to call next(...) inside your Filter
|
|
||||||
type FilterChain func(next Filter) Filter
|
|
||||||
|
|
||||||
// Filter's behavior is a little big strange.
|
|
||||||
// it's only be called when users call methods of Ormer
|
|
||||||
// return value is an array. it's a little bit hard to understand,
|
|
||||||
// for example, the Ormer's Read method only return error
|
|
||||||
// so the filter processing this method should return an array whose first element is error
|
|
||||||
// and, Ormer's ReadOrCreateWithCtx return three values, so the Filter's result should contains three values
|
|
||||||
type Filter func(ctx context.Context, inv *Invocation) []interface{}
|
|
||||||
|
|
||||||
var globalFilterChains = make([]FilterChain, 0, 4)
|
|
||||||
|
|
||||||
// AddGlobalFilterChain adds a new FilterChain
|
|
||||||
// All orm instances built after this invocation will use this filterChain,
|
|
||||||
// but instances built before this invocation will not be affected
|
|
||||||
func AddGlobalFilterChain(filterChain ...FilterChain) {
|
|
||||||
globalFilterChains = append(globalFilterChains, filterChain...)
|
|
||||||
}
|
|
@ -1,534 +0,0 @@
|
|||||||
// Copyright 2020 beego
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/core/logs"
|
|
||||||
"github.com/beego/beego/v2/core/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TxNameKey = "TxName"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ Ormer = new(filterOrmDecorator)
|
|
||||||
_ TxOrmer = new(filterOrmDecorator)
|
|
||||||
)
|
|
||||||
|
|
||||||
type filterOrmDecorator struct {
|
|
||||||
ormer
|
|
||||||
TxBeginner
|
|
||||||
TxCommitter
|
|
||||||
|
|
||||||
root Filter
|
|
||||||
|
|
||||||
insideTx bool
|
|
||||||
txStartTime time.Time
|
|
||||||
txName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFilterOrmDecorator(delegate Ormer, filterChains ...FilterChain) Ormer {
|
|
||||||
res := &filterOrmDecorator{
|
|
||||||
ormer: delegate,
|
|
||||||
TxBeginner: delegate,
|
|
||||||
root: func(ctx context.Context, inv *Invocation) []interface{} {
|
|
||||||
return inv.execute(ctx)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(filterChains) - 1; i >= 0; i-- {
|
|
||||||
node := filterChains[i]
|
|
||||||
res.root = node(res.root)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFilterTxOrmDecorator(delegate TxOrmer, root Filter, txName string) TxOrmer {
|
|
||||||
res := &filterOrmDecorator{
|
|
||||||
ormer: delegate,
|
|
||||||
TxCommitter: delegate,
|
|
||||||
root: root,
|
|
||||||
insideTx: true,
|
|
||||||
txStartTime: time.Now(),
|
|
||||||
txName: txName,
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Read(md interface{}, cols ...string) error {
|
|
||||||
return f.ReadWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "ReadWithCtx",
|
|
||||||
Args: []interface{}{md, cols},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
err := f.ormer.ReadWithCtx(c, md, cols...)
|
|
||||||
return []interface{}{err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return f.convertError(res[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) ReadForUpdate(md interface{}, cols ...string) error {
|
|
||||||
return f.ReadForUpdateWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "ReadForUpdateWithCtx",
|
|
||||||
Args: []interface{}{md, cols},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
err := f.ormer.ReadForUpdateWithCtx(c, md, cols...)
|
|
||||||
return []interface{}{err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return f.convertError(res[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
|
||||||
return f.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "ReadOrCreateWithCtx",
|
|
||||||
Args: []interface{}{md, col1, cols},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
ok, res, err := f.ormer.ReadOrCreateWithCtx(c, md, col1, cols...)
|
|
||||||
return []interface{}{ok, res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(bool), res[1].(int64), f.convertError(res[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
|
||||||
return f.LoadRelatedWithCtx(context.Background(), md, name, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "LoadRelatedWithCtx",
|
|
||||||
Args: []interface{}{md, name, args},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.ormer.LoadRelatedWithCtx(c, md, name, args...)
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(int64), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) QueryM2M(md interface{}, name string) QueryM2Mer {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "QueryM2M",
|
|
||||||
Args: []interface{}{md, name},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res := f.ormer.QueryM2M(md, name)
|
|
||||||
return []interface{}{res}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
if res[0] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return res[0].(QueryM2Mer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
|
||||||
func (f *filterOrmDecorator) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
|
|
||||||
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` on QueryM2Mer suffix as replacement.")
|
|
||||||
return f.QueryM2M(md, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
|
|
||||||
var (
|
|
||||||
name string
|
|
||||||
md interface{}
|
|
||||||
mi *modelInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
if table, ok := ptrStructOrTableName.(string); ok {
|
|
||||||
name = table
|
|
||||||
} else {
|
|
||||||
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
|
||||||
md = ptrStructOrTableName
|
|
||||||
}
|
|
||||||
|
|
||||||
if m, ok := defaultModelCache.getByFullName(name); ok {
|
|
||||||
mi = m
|
|
||||||
}
|
|
||||||
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "QueryTable",
|
|
||||||
Args: []interface{}{ptrStructOrTableName},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res := f.ormer.QueryTable(ptrStructOrTableName)
|
|
||||||
return []interface{}{res}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
|
|
||||||
if res[0] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return res[0].(QuerySeter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
|
||||||
func (f *filterOrmDecorator) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) QuerySeter {
|
|
||||||
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx`on QuerySeter suffix as replacement.")
|
|
||||||
return f.QueryTable(ptrStructOrTableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) DBStats() *sql.DBStats {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "DBStats",
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res := f.ormer.DBStats()
|
|
||||||
return []interface{}{res}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
|
|
||||||
if res[0] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return res[0].(*sql.DBStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Insert(md interface{}) (int64, error) {
|
|
||||||
return f.InsertWithCtx(context.Background(), md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "InsertWithCtx",
|
|
||||||
Args: []interface{}{md},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.ormer.InsertWithCtx(c, md)
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(int64), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
|
||||||
return f.InsertOrUpdateWithCtx(context.Background(), md, colConflitAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "InsertOrUpdateWithCtx",
|
|
||||||
Args: []interface{}{md, colConflitAndArgs},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.ormer.InsertOrUpdateWithCtx(c, md, colConflitAndArgs...)
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(int64), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
|
||||||
return f.InsertMultiWithCtx(context.Background(), bulk, mds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertMultiWithCtx uses the first element's model info
|
|
||||||
func (f *filterOrmDecorator) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
|
||||||
var (
|
|
||||||
md interface{}
|
|
||||||
mi *modelInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
sind := reflect.Indirect(reflect.ValueOf(mds))
|
|
||||||
|
|
||||||
if (sind.Kind() == reflect.Array || sind.Kind() == reflect.Slice) && sind.Len() > 0 {
|
|
||||||
ind := reflect.Indirect(sind.Index(0))
|
|
||||||
md = ind.Interface()
|
|
||||||
mi, _ = defaultModelCache.getByMd(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "InsertMultiWithCtx",
|
|
||||||
Args: []interface{}{bulk, mds},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.ormer.InsertMultiWithCtx(c, bulk, mds)
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(int64), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Update(md interface{}, cols ...string) (int64, error) {
|
|
||||||
return f.UpdateWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "UpdateWithCtx",
|
|
||||||
Args: []interface{}{md, cols},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.ormer.UpdateWithCtx(c, md, cols...)
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(int64), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Delete(md interface{}, cols ...string) (int64, error) {
|
|
||||||
return f.DeleteWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
|
||||||
mi, _ := defaultModelCache.getByMd(md)
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "DeleteWithCtx",
|
|
||||||
Args: []interface{}{md, cols},
|
|
||||||
Md: md,
|
|
||||||
mi: mi,
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.ormer.DeleteWithCtx(c, md, cols...)
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(int64), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Raw(query string, args ...interface{}) RawSeter {
|
|
||||||
return f.RawWithCtx(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "RawWithCtx",
|
|
||||||
Args: []interface{}{query, args},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res := f.ormer.RawWithCtx(c, query, args...)
|
|
||||||
return []interface{}{res}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
|
|
||||||
if res[0] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return res[0].(RawSeter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Driver() Driver {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "Driver",
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res := f.ormer.Driver()
|
|
||||||
return []interface{}{res}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
if res[0] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return res[0].(Driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Begin() (TxOrmer, error) {
|
|
||||||
return f.BeginWithCtxAndOpts(context.Background(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
|
||||||
return f.BeginWithCtxAndOpts(ctx, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
|
||||||
return f.BeginWithCtxAndOpts(context.Background(), opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "BeginWithCtxAndOpts",
|
|
||||||
Args: []interface{}{opts},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
res, err := f.TxBeginner.BeginWithCtxAndOpts(c, opts)
|
|
||||||
res = NewFilterTxOrmDecorator(res, f.root, getTxNameFromCtx(c))
|
|
||||||
return []interface{}{res, err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return res[0].(TxOrmer), f.convertError(res[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return f.DoTxWithCtxAndOpts(context.Background(), nil, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return f.DoTxWithCtxAndOpts(ctx, nil, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return f.DoTxWithCtxAndOpts(context.Background(), opts, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "DoTxWithCtxAndOpts",
|
|
||||||
Args: []interface{}{opts, task},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
TxName: getTxNameFromCtx(ctx),
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
err := doTxTemplate(c, f, opts, task)
|
|
||||||
return []interface{}{err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(ctx, inv)
|
|
||||||
return f.convertError(res[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Commit() error {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "Commit",
|
|
||||||
Args: []interface{}{},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
TxName: f.txName,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
err := f.TxCommitter.Commit()
|
|
||||||
return []interface{}{err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
return f.convertError(res[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) Rollback() error {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "Rollback",
|
|
||||||
Args: []interface{}{},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
TxName: f.txName,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
err := f.TxCommitter.Rollback()
|
|
||||||
return []interface{}{err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
return f.convertError(res[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filterOrmDecorator) RollbackUnlessCommit() error {
|
|
||||||
inv := &Invocation{
|
|
||||||
Method: "RollbackUnlessCommit",
|
|
||||||
Args: []interface{}{},
|
|
||||||
InsideTx: f.insideTx,
|
|
||||||
TxStartTime: f.txStartTime,
|
|
||||||
TxName: f.txName,
|
|
||||||
f: func(c context.Context) []interface{} {
|
|
||||||
err := f.TxCommitter.RollbackUnlessCommit()
|
|
||||||
return []interface{}{err}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res := f.root(context.Background(), inv)
|
|
||||||
return f.convertError(res[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*filterOrmDecorator) convertError(v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTxNameFromCtx(ctx context.Context) string {
|
|
||||||
txName := ""
|
|
||||||
if n, ok := ctx.Value(TxNameKey).(string); ok {
|
|
||||||
txName = n
|
|
||||||
}
|
|
||||||
return txName
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
// Copyright 2020 beego-dev
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package hints
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/beego/beego/v2/core/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// query level
|
|
||||||
KeyForceIndex = iota
|
|
||||||
KeyUseIndex
|
|
||||||
KeyIgnoreIndex
|
|
||||||
KeyForUpdate
|
|
||||||
KeyLimit
|
|
||||||
KeyOffset
|
|
||||||
KeyOrderBy
|
|
||||||
KeyRelDepth
|
|
||||||
)
|
|
||||||
|
|
||||||
type Hint struct {
|
|
||||||
key interface{}
|
|
||||||
value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ utils.KV = new(Hint)
|
|
||||||
|
|
||||||
// GetKey return key
|
|
||||||
func (s *Hint) GetKey() interface{} {
|
|
||||||
return s.key
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue return value
|
|
||||||
func (s *Hint) GetValue() interface{} {
|
|
||||||
return s.value
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ utils.KV = new(Hint)
|
|
||||||
|
|
||||||
// ForceIndex return a hint about ForceIndex
|
|
||||||
func ForceIndex(indexes ...string) *Hint {
|
|
||||||
return NewHint(KeyForceIndex, indexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseIndex return a hint about UseIndex
|
|
||||||
func UseIndex(indexes ...string) *Hint {
|
|
||||||
return NewHint(KeyUseIndex, indexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IgnoreIndex return a hint about IgnoreIndex
|
|
||||||
func IgnoreIndex(indexes ...string) *Hint {
|
|
||||||
return NewHint(KeyIgnoreIndex, indexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForUpdate return a hint about ForUpdate
|
|
||||||
func ForUpdate() *Hint {
|
|
||||||
return NewHint(KeyForUpdate, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultRelDepth return a hint about DefaultRelDepth
|
|
||||||
func DefaultRelDepth() *Hint {
|
|
||||||
return NewHint(KeyRelDepth, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelDepth return a hint about RelDepth
|
|
||||||
func RelDepth(d int) *Hint {
|
|
||||||
return NewHint(KeyRelDepth, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit return a hint about Limit
|
|
||||||
func Limit(d int64) *Hint {
|
|
||||||
return NewHint(KeyLimit, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset return a hint about Offset
|
|
||||||
func Offset(d int64) *Hint {
|
|
||||||
return NewHint(KeyOffset, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OrderBy return a hint about OrderBy
|
|
||||||
func OrderBy(s string) *Hint {
|
|
||||||
return NewHint(KeyOrderBy, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHint return a hint
|
|
||||||
func NewHint(key interface{}, value interface{}) *Hint {
|
|
||||||
return &Hint{
|
|
||||||
key: key,
|
|
||||||
value: value,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
// Copyright 2020 beego
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Invocation represents an "Orm" invocation
|
|
||||||
type Invocation struct {
|
|
||||||
Method string
|
|
||||||
// Md may be nil in some cases. It depends on method
|
|
||||||
Md interface{}
|
|
||||||
// the args are all arguments except context.Context
|
|
||||||
Args []interface{}
|
|
||||||
|
|
||||||
mi *modelInfo
|
|
||||||
// f is the Orm operation
|
|
||||||
f func(ctx context.Context) []interface{}
|
|
||||||
|
|
||||||
// insideTx indicates whether this is inside a transaction
|
|
||||||
InsideTx bool
|
|
||||||
TxStartTime time.Time
|
|
||||||
TxName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (inv *Invocation) GetTableName() string {
|
|
||||||
if inv.mi != nil {
|
|
||||||
return inv.mi.table
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (inv *Invocation) execute(ctx context.Context) []interface{} {
|
|
||||||
return inv.f(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPkFieldName return the primary key of this table
|
|
||||||
// if not found, "" is returned
|
|
||||||
func (inv *Invocation) GetPkFieldName() string {
|
|
||||||
if inv.mi.fields.pk != nil {
|
|
||||||
return inv.mi.fields.pk.name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
@ -1,573 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
odCascade = "cascade"
|
|
||||||
odSetNULL = "set_null"
|
|
||||||
odSetDefault = "set_default"
|
|
||||||
odDoNothing = "do_nothing"
|
|
||||||
defaultStructTagName = "orm"
|
|
||||||
defaultStructTagDelim = ";"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultModelCache = NewModelCacheHandler()
|
|
||||||
|
|
||||||
// model info collection
|
|
||||||
type modelCache struct {
|
|
||||||
sync.RWMutex // only used outsite for bootStrap
|
|
||||||
orders []string
|
|
||||||
cache map[string]*modelInfo
|
|
||||||
cacheByFullName map[string]*modelInfo
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModelCacheHandler generator of modelCache
|
|
||||||
func NewModelCacheHandler() *modelCache {
|
|
||||||
return &modelCache{
|
|
||||||
cache: make(map[string]*modelInfo),
|
|
||||||
cacheByFullName: make(map[string]*modelInfo),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all model info
|
|
||||||
func (mc *modelCache) all() map[string]*modelInfo {
|
|
||||||
m := make(map[string]*modelInfo, len(mc.cache))
|
|
||||||
for k, v := range mc.cache {
|
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// get ordered model info
|
|
||||||
func (mc *modelCache) allOrdered() []*modelInfo {
|
|
||||||
m := make([]*modelInfo, 0, len(mc.orders))
|
|
||||||
for _, table := range mc.orders {
|
|
||||||
m = append(m, mc.cache[table])
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// get model info by table name
|
|
||||||
func (mc *modelCache) get(table string) (mi *modelInfo, ok bool) {
|
|
||||||
mi, ok = mc.cache[table]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get model info by full name
|
|
||||||
func (mc *modelCache) getByFullName(name string) (mi *modelInfo, ok bool) {
|
|
||||||
mi, ok = mc.cacheByFullName[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *modelCache) getByMd(md interface{}) (*modelInfo, bool) {
|
|
||||||
val := reflect.ValueOf(md)
|
|
||||||
ind := reflect.Indirect(val)
|
|
||||||
typ := ind.Type()
|
|
||||||
name := getFullName(typ)
|
|
||||||
return mc.getByFullName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set model info to collection
|
|
||||||
func (mc *modelCache) set(table string, mi *modelInfo) *modelInfo {
|
|
||||||
mii := mc.cache[table]
|
|
||||||
mc.cache[table] = mi
|
|
||||||
mc.cacheByFullName[mi.fullName] = mi
|
|
||||||
if mii == nil {
|
|
||||||
mc.orders = append(mc.orders, table)
|
|
||||||
}
|
|
||||||
return mii
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean all model info.
|
|
||||||
func (mc *modelCache) clean() {
|
|
||||||
mc.Lock()
|
|
||||||
defer mc.Unlock()
|
|
||||||
|
|
||||||
mc.orders = make([]string, 0)
|
|
||||||
mc.cache = make(map[string]*modelInfo)
|
|
||||||
mc.cacheByFullName = make(map[string]*modelInfo)
|
|
||||||
mc.done = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// bootstrap bootstrap for models
|
|
||||||
func (mc *modelCache) bootstrap() {
|
|
||||||
mc.Lock()
|
|
||||||
defer mc.Unlock()
|
|
||||||
if mc.done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
models map[string]*modelInfo
|
|
||||||
)
|
|
||||||
if dataBaseCache.getDefault() == nil {
|
|
||||||
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
|
|
||||||
// set rel and reverse model
|
|
||||||
// RelManyToMany set the relTable
|
|
||||||
models = mc.all()
|
|
||||||
for _, mi := range models {
|
|
||||||
for _, fi := range mi.fields.columns {
|
|
||||||
if fi.rel || fi.reverse {
|
|
||||||
elm := fi.addrValue.Type().Elem()
|
|
||||||
if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany {
|
|
||||||
elm = elm.Elem()
|
|
||||||
}
|
|
||||||
// check the rel or reverse model already register
|
|
||||||
name := getFullName(elm)
|
|
||||||
mii, ok := mc.getByFullName(name)
|
|
||||||
if !ok || mii.pkg != elm.PkgPath() {
|
|
||||||
err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
fi.relModelInfo = mii
|
|
||||||
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelManyToMany:
|
|
||||||
if fi.relThrough != "" {
|
|
||||||
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
|
|
||||||
pn := fi.relThrough[:i]
|
|
||||||
rmi, ok := mc.getByFullName(fi.relThrough)
|
|
||||||
if !ok || pn != rmi.pkg {
|
|
||||||
err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
fi.relThroughModelInfo = rmi
|
|
||||||
fi.relTable = rmi.table
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i := newM2MModelInfo(mi, mii)
|
|
||||||
if fi.relTable != "" {
|
|
||||||
i.table = fi.relTable
|
|
||||||
}
|
|
||||||
if v := mc.set(i.table, i); v != nil {
|
|
||||||
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
fi.relTable = i.table
|
|
||||||
fi.relThroughModelInfo = i
|
|
||||||
}
|
|
||||||
|
|
||||||
fi.relThroughModelInfo.isThrough = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the rel filed while the relModelInfo also has filed point to current model
|
|
||||||
// if not exist, add a new field to the relModelInfo
|
|
||||||
models = mc.all()
|
|
||||||
for _, mi := range models {
|
|
||||||
for _, fi := range mi.fields.fieldsRel {
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelForeignKey, RelOneToOne, RelManyToMany:
|
|
||||||
inModel := false
|
|
||||||
for _, ffi := range fi.relModelInfo.fields.fieldsReverse {
|
|
||||||
if ffi.relModelInfo == mi {
|
|
||||||
inModel = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !inModel {
|
|
||||||
rmi := fi.relModelInfo
|
|
||||||
ffi := new(fieldInfo)
|
|
||||||
ffi.name = mi.name
|
|
||||||
ffi.column = ffi.name
|
|
||||||
ffi.fullName = rmi.fullName + "." + ffi.name
|
|
||||||
ffi.reverse = true
|
|
||||||
ffi.relModelInfo = mi
|
|
||||||
ffi.mi = rmi
|
|
||||||
if fi.fieldType == RelOneToOne {
|
|
||||||
ffi.fieldType = RelReverseOne
|
|
||||||
} else {
|
|
||||||
ffi.fieldType = RelReverseMany
|
|
||||||
}
|
|
||||||
if !rmi.fields.Add(ffi) {
|
|
||||||
added := false
|
|
||||||
for cnt := 0; cnt < 5; cnt++ {
|
|
||||||
ffi.name = fmt.Sprintf("%s%d", mi.name, cnt)
|
|
||||||
ffi.column = ffi.name
|
|
||||||
ffi.fullName = rmi.fullName + "." + ffi.name
|
|
||||||
if added = rmi.fields.Add(ffi); added {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !added {
|
|
||||||
panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
models = mc.all()
|
|
||||||
for _, mi := range models {
|
|
||||||
for _, fi := range mi.fields.fieldsRel {
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelManyToMany:
|
|
||||||
for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel {
|
|
||||||
switch ffi.fieldType {
|
|
||||||
case RelOneToOne, RelForeignKey:
|
|
||||||
if ffi.relModelInfo == fi.relModelInfo {
|
|
||||||
fi.reverseFieldInfoTwo = ffi
|
|
||||||
}
|
|
||||||
if ffi.relModelInfo == mi {
|
|
||||||
fi.reverseField = ffi.name
|
|
||||||
fi.reverseFieldInfo = ffi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fi.reverseFieldInfoTwo == nil {
|
|
||||||
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
|
||||||
fi.relThroughModelInfo.fullName)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
models = mc.all()
|
|
||||||
for _, mi := range models {
|
|
||||||
for _, fi := range mi.fields.fieldsReverse {
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelReverseOne:
|
|
||||||
found := false
|
|
||||||
mForA:
|
|
||||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] {
|
|
||||||
if ffi.relModelInfo == mi {
|
|
||||||
found = true
|
|
||||||
fi.reverseField = ffi.name
|
|
||||||
fi.reverseFieldInfo = ffi
|
|
||||||
|
|
||||||
ffi.reverseField = fi.name
|
|
||||||
ffi.reverseFieldInfo = fi
|
|
||||||
break mForA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
case RelReverseMany:
|
|
||||||
found := false
|
|
||||||
mForB:
|
|
||||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] {
|
|
||||||
if ffi.relModelInfo == mi {
|
|
||||||
found = true
|
|
||||||
fi.reverseField = ffi.name
|
|
||||||
fi.reverseFieldInfo = ffi
|
|
||||||
|
|
||||||
ffi.reverseField = fi.name
|
|
||||||
ffi.reverseFieldInfo = fi
|
|
||||||
|
|
||||||
break mForB
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
mForC:
|
|
||||||
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] {
|
|
||||||
conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough ||
|
|
||||||
fi.relTable != "" && fi.relTable == ffi.relTable ||
|
|
||||||
fi.relThrough == "" && fi.relTable == ""
|
|
||||||
if ffi.relModelInfo == mi && conditions {
|
|
||||||
found = true
|
|
||||||
|
|
||||||
fi.reverseField = ffi.reverseFieldInfoTwo.name
|
|
||||||
fi.reverseFieldInfo = ffi.reverseFieldInfoTwo
|
|
||||||
fi.relThroughModelInfo = ffi.relThroughModelInfo
|
|
||||||
fi.reverseFieldInfoTwo = ffi.reverseFieldInfo
|
|
||||||
fi.reverseFieldInfoM2M = ffi
|
|
||||||
ffi.reverseFieldInfoM2M = fi
|
|
||||||
|
|
||||||
break mForC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end:
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
debug.PrintStack()
|
|
||||||
}
|
|
||||||
mc.done = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// register register models to model cache
|
|
||||||
func (mc *modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, models ...interface{}) (err error) {
|
|
||||||
for _, model := range models {
|
|
||||||
val := reflect.ValueOf(model)
|
|
||||||
typ := reflect.Indirect(val).Type()
|
|
||||||
|
|
||||||
if val.Kind() != reflect.Ptr {
|
|
||||||
err = fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// For this case:
|
|
||||||
// u := &User{}
|
|
||||||
// registerModel(&u)
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
|
||||||
err = fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if val.Elem().Kind() == reflect.Slice {
|
|
||||||
val = reflect.New(val.Elem().Type().Elem())
|
|
||||||
}
|
|
||||||
table := getTableName(val)
|
|
||||||
|
|
||||||
if prefixOrSuffixStr != "" {
|
|
||||||
if prefixOrSuffix {
|
|
||||||
table = prefixOrSuffixStr + table
|
|
||||||
} else {
|
|
||||||
table = table + prefixOrSuffixStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// models's fullname is pkgpath + struct name
|
|
||||||
name := getFullName(typ)
|
|
||||||
if _, ok := mc.getByFullName(name); ok {
|
|
||||||
err = fmt.Errorf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := mc.get(table); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mi := newModelInfo(val)
|
|
||||||
if mi.fields.pk == nil {
|
|
||||||
outFor:
|
|
||||||
for _, fi := range mi.fields.fieldsDB {
|
|
||||||
if strings.ToLower(fi.name) == "id" {
|
|
||||||
switch fi.addrValue.Elem().Kind() {
|
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
|
||||||
fi.auto = true
|
|
||||||
fi.pk = true
|
|
||||||
mi.fields.pk = fi
|
|
||||||
break outFor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mi.table = table
|
|
||||||
mi.pkg = typ.PkgPath()
|
|
||||||
mi.model = model
|
|
||||||
mi.manual = true
|
|
||||||
|
|
||||||
mc.set(table, mi)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDbDropSQL get database scheme drop sql queries
|
|
||||||
func (mc *modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
|
|
||||||
if len(mc.cache) == 0 {
|
|
||||||
err = errors.New("no Model found, need register your model")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Q := al.DbBaser.TableQuote()
|
|
||||||
|
|
||||||
for _, mi := range mc.allOrdered() {
|
|
||||||
queries = append(queries, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q))
|
|
||||||
}
|
|
||||||
return queries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDbCreateSQL get database scheme creation sql queries
|
|
||||||
func (mc *modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) {
|
|
||||||
if len(mc.cache) == 0 {
|
|
||||||
err = errors.New("no Model found, need register your model")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Q := al.DbBaser.TableQuote()
|
|
||||||
T := al.DbBaser.DbTypes()
|
|
||||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
|
||||||
|
|
||||||
tableIndexes = make(map[string][]dbIndex)
|
|
||||||
|
|
||||||
for _, mi := range mc.allOrdered() {
|
|
||||||
sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
|
||||||
sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName)
|
|
||||||
sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
|
||||||
|
|
||||||
sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q)
|
|
||||||
|
|
||||||
columns := make([]string, 0, len(mi.fields.fieldsDB))
|
|
||||||
|
|
||||||
sqlIndexes := [][]string{}
|
|
||||||
var commentIndexes []int // store comment indexes for postgres
|
|
||||||
|
|
||||||
for i, fi := range mi.fields.fieldsDB {
|
|
||||||
column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q)
|
|
||||||
col := getColumnTyp(al, fi)
|
|
||||||
|
|
||||||
if fi.auto {
|
|
||||||
switch al.Driver {
|
|
||||||
case DRSqlite, DRPostgres:
|
|
||||||
column += T["auto"]
|
|
||||||
default:
|
|
||||||
column += col + " " + T["auto"]
|
|
||||||
}
|
|
||||||
} else if fi.pk {
|
|
||||||
column += col + " " + T["pk"]
|
|
||||||
} else {
|
|
||||||
column += col
|
|
||||||
|
|
||||||
if !fi.null {
|
|
||||||
column += " " + "NOT NULL"
|
|
||||||
}
|
|
||||||
|
|
||||||
// if fi.initial.String() != "" {
|
|
||||||
// column += " DEFAULT " + fi.initial.String()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Append attribute DEFAULT
|
|
||||||
column += getColumnDefault(fi)
|
|
||||||
|
|
||||||
if fi.unique {
|
|
||||||
column += " " + "UNIQUE"
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.index {
|
|
||||||
sqlIndexes = append(sqlIndexes, []string{fi.column})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(column, "%COL%") {
|
|
||||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.description != "" && al.Driver != DRSqlite {
|
|
||||||
if al.Driver == DRPostgres {
|
|
||||||
commentIndexes = append(commentIndexes, i)
|
|
||||||
} else {
|
|
||||||
column += " " + fmt.Sprintf("COMMENT '%s'", fi.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
columns = append(columns, column)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mi.model != nil {
|
|
||||||
allnames := getTableUnique(mi.addrField)
|
|
||||||
if !mi.manual && len(mi.uniques) > 0 {
|
|
||||||
allnames = append(allnames, mi.uniques)
|
|
||||||
}
|
|
||||||
for _, names := range allnames {
|
|
||||||
cols := make([]string, 0, len(names))
|
|
||||||
for _, name := range names {
|
|
||||||
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
|
||||||
cols = append(cols, fi.column)
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q)
|
|
||||||
columns = append(columns, column)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sql += strings.Join(columns, ",\n")
|
|
||||||
sql += "\n)"
|
|
||||||
|
|
||||||
if al.Driver == DRMySQL {
|
|
||||||
var engine string
|
|
||||||
if mi.model != nil {
|
|
||||||
engine = getTableEngine(mi.addrField)
|
|
||||||
}
|
|
||||||
if engine == "" {
|
|
||||||
engine = al.Engine
|
|
||||||
}
|
|
||||||
sql += " ENGINE=" + engine
|
|
||||||
}
|
|
||||||
|
|
||||||
sql += ";"
|
|
||||||
if al.Driver == DRPostgres && len(commentIndexes) > 0 {
|
|
||||||
// append comments for postgres only
|
|
||||||
for _, index := range commentIndexes {
|
|
||||||
sql += fmt.Sprintf("\nCOMMENT ON COLUMN %s%s%s.%s%s%s is '%s';",
|
|
||||||
Q,
|
|
||||||
mi.table,
|
|
||||||
Q,
|
|
||||||
Q,
|
|
||||||
mi.fields.fieldsDB[index].column,
|
|
||||||
Q,
|
|
||||||
mi.fields.fieldsDB[index].description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queries = append(queries, sql)
|
|
||||||
|
|
||||||
if mi.model != nil {
|
|
||||||
for _, names := range getTableIndex(mi.addrField) {
|
|
||||||
cols := make([]string, 0, len(names))
|
|
||||||
for _, name := range names {
|
|
||||||
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
|
||||||
cols = append(cols, fi.column)
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlIndexes = append(sqlIndexes, cols)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, names := range sqlIndexes {
|
|
||||||
name := mi.table + "_" + strings.Join(names, "_")
|
|
||||||
cols := strings.Join(names, sep)
|
|
||||||
sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q)
|
|
||||||
|
|
||||||
index := dbIndex{}
|
|
||||||
index.Table = mi.table
|
|
||||||
index.Name = name
|
|
||||||
index.SQL = sql
|
|
||||||
|
|
||||||
tableIndexes[mi.table] = append(tableIndexes[mi.table], index)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetModelCache Clean model cache. Then you can re-RegisterModel.
|
|
||||||
// Common use this api for test case.
|
|
||||||
func ResetModelCache() {
|
|
||||||
defaultModelCache.clean()
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
// RegisterModel register models
|
|
||||||
func RegisterModel(models ...interface{}) {
|
|
||||||
RegisterModelWithPrefix("", models...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterModelWithPrefix register models with a prefix
|
|
||||||
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
|
||||||
if err := defaultModelCache.register(prefix, true, models...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterModelWithSuffix register models with a suffix
|
|
||||||
func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
|
||||||
if err := defaultModelCache.register(suffix, false, models...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BootStrap bootstrap models.
|
|
||||||
// make all model parsed and can not add more models
|
|
||||||
func BootStrap() {
|
|
||||||
defaultModelCache.bootstrap()
|
|
||||||
}
|
|
@ -1,485 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errSkipField = errors.New("skip field")
|
|
||||||
|
|
||||||
// field info collection
|
|
||||||
type fields struct {
|
|
||||||
pk *fieldInfo
|
|
||||||
columns map[string]*fieldInfo
|
|
||||||
fields map[string]*fieldInfo
|
|
||||||
fieldsLow map[string]*fieldInfo
|
|
||||||
fieldsByType map[int][]*fieldInfo
|
|
||||||
fieldsRel []*fieldInfo
|
|
||||||
fieldsReverse []*fieldInfo
|
|
||||||
fieldsDB []*fieldInfo
|
|
||||||
rels []*fieldInfo
|
|
||||||
orders []string
|
|
||||||
dbcols []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// add field info
|
|
||||||
func (f *fields) Add(fi *fieldInfo) (added bool) {
|
|
||||||
if f.fields[fi.name] == nil && f.columns[fi.column] == nil {
|
|
||||||
f.columns[fi.column] = fi
|
|
||||||
f.fields[fi.name] = fi
|
|
||||||
f.fieldsLow[strings.ToLower(fi.name)] = fi
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, ok := f.fieldsByType[fi.fieldType]; !ok {
|
|
||||||
f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0)
|
|
||||||
}
|
|
||||||
f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi)
|
|
||||||
f.orders = append(f.orders, fi.column)
|
|
||||||
if fi.dbcol {
|
|
||||||
f.dbcols = append(f.dbcols, fi.column)
|
|
||||||
f.fieldsDB = append(f.fieldsDB, fi)
|
|
||||||
}
|
|
||||||
if fi.rel {
|
|
||||||
f.fieldsRel = append(f.fieldsRel, fi)
|
|
||||||
}
|
|
||||||
if fi.reverse {
|
|
||||||
f.fieldsReverse = append(f.fieldsReverse, fi)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// get field info by name
|
|
||||||
func (f *fields) GetByName(name string) *fieldInfo {
|
|
||||||
return f.fields[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// get field info by column name
|
|
||||||
func (f *fields) GetByColumn(column string) *fieldInfo {
|
|
||||||
return f.columns[column]
|
|
||||||
}
|
|
||||||
|
|
||||||
// get field info by string, name is prior
|
|
||||||
func (f *fields) GetByAny(name string) (*fieldInfo, bool) {
|
|
||||||
if fi, ok := f.fields[name]; ok {
|
|
||||||
return fi, ok
|
|
||||||
}
|
|
||||||
if fi, ok := f.fieldsLow[strings.ToLower(name)]; ok {
|
|
||||||
return fi, ok
|
|
||||||
}
|
|
||||||
if fi, ok := f.columns[name]; ok {
|
|
||||||
return fi, ok
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new field info collection
|
|
||||||
func newFields() *fields {
|
|
||||||
f := new(fields)
|
|
||||||
f.fields = make(map[string]*fieldInfo)
|
|
||||||
f.fieldsLow = make(map[string]*fieldInfo)
|
|
||||||
f.columns = make(map[string]*fieldInfo)
|
|
||||||
f.fieldsByType = make(map[int][]*fieldInfo)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// single field info
|
|
||||||
type fieldInfo struct {
|
|
||||||
dbcol bool // table column fk and onetoone
|
|
||||||
inModel bool
|
|
||||||
auto bool
|
|
||||||
pk bool
|
|
||||||
null bool
|
|
||||||
index bool
|
|
||||||
unique bool
|
|
||||||
colDefault bool // whether has default tag
|
|
||||||
toText bool
|
|
||||||
autoNow bool
|
|
||||||
autoNowAdd bool
|
|
||||||
rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true
|
|
||||||
reverse bool
|
|
||||||
isFielder bool // implement Fielder interface
|
|
||||||
mi *modelInfo
|
|
||||||
fieldIndex []int
|
|
||||||
fieldType int
|
|
||||||
name string
|
|
||||||
fullName string
|
|
||||||
column string
|
|
||||||
addrValue reflect.Value
|
|
||||||
sf reflect.StructField
|
|
||||||
initial StrTo // store the default value
|
|
||||||
size int
|
|
||||||
reverseField string
|
|
||||||
reverseFieldInfo *fieldInfo
|
|
||||||
reverseFieldInfoTwo *fieldInfo
|
|
||||||
reverseFieldInfoM2M *fieldInfo
|
|
||||||
relTable string
|
|
||||||
relThrough string
|
|
||||||
relThroughModelInfo *modelInfo
|
|
||||||
relModelInfo *modelInfo
|
|
||||||
digits int
|
|
||||||
decimals int
|
|
||||||
onDelete string
|
|
||||||
description string
|
|
||||||
timePrecision *int
|
|
||||||
}
|
|
||||||
|
|
||||||
// new field info
|
|
||||||
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) {
|
|
||||||
var (
|
|
||||||
tag string
|
|
||||||
tagValue string
|
|
||||||
initial StrTo // store the default value
|
|
||||||
fieldType int
|
|
||||||
attrs map[string]bool
|
|
||||||
tags map[string]string
|
|
||||||
addrField reflect.Value
|
|
||||||
)
|
|
||||||
|
|
||||||
fi = new(fieldInfo)
|
|
||||||
|
|
||||||
// if field which CanAddr is the follow type
|
|
||||||
// A value is addressable if it is an element of a slice,
|
|
||||||
// an element of an addressable array, a field of an
|
|
||||||
// addressable struct, or the result of dereferencing a pointer.
|
|
||||||
addrField = field
|
|
||||||
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
|
||||||
addrField = field.Addr()
|
|
||||||
if _, ok := addrField.Interface().(Fielder); !ok {
|
|
||||||
if field.Kind() == reflect.Slice {
|
|
||||||
addrField = field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs, tags = parseStructTag(sf.Tag.Get(defaultStructTagName))
|
|
||||||
|
|
||||||
if _, ok := attrs["-"]; ok {
|
|
||||||
return nil, errSkipField
|
|
||||||
}
|
|
||||||
|
|
||||||
digits := tags["digits"]
|
|
||||||
decimals := tags["decimals"]
|
|
||||||
size := tags["size"]
|
|
||||||
onDelete := tags["on_delete"]
|
|
||||||
precision := tags["precision"]
|
|
||||||
initial.Clear()
|
|
||||||
if v, ok := tags["default"]; ok {
|
|
||||||
initial.Set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkType:
|
|
||||||
switch f := addrField.Interface().(type) {
|
|
||||||
case Fielder:
|
|
||||||
fi.isFielder = true
|
|
||||||
if field.Kind() == reflect.Ptr {
|
|
||||||
err = fmt.Errorf("the model Fielder can not be use ptr")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
fieldType = f.FieldType()
|
|
||||||
if fieldType&IsRelField > 0 {
|
|
||||||
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/beego/beego/v2/blob/master/orm/models_fields.go#L24-L42")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
tag = "rel"
|
|
||||||
tagValue = tags[tag]
|
|
||||||
if tagValue != "" {
|
|
||||||
switch tagValue {
|
|
||||||
case "fk":
|
|
||||||
fieldType = RelForeignKey
|
|
||||||
break checkType
|
|
||||||
case "one":
|
|
||||||
fieldType = RelOneToOne
|
|
||||||
break checkType
|
|
||||||
case "m2m":
|
|
||||||
fieldType = RelManyToMany
|
|
||||||
if tv := tags["rel_table"]; tv != "" {
|
|
||||||
fi.relTable = tv
|
|
||||||
} else if tv := tags["rel_through"]; tv != "" {
|
|
||||||
fi.relThrough = tv
|
|
||||||
}
|
|
||||||
break checkType
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("rel only allow these value: fk, one, m2m")
|
|
||||||
goto wrongTag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tag = "reverse"
|
|
||||||
tagValue = tags[tag]
|
|
||||||
if tagValue != "" {
|
|
||||||
switch tagValue {
|
|
||||||
case "one":
|
|
||||||
fieldType = RelReverseOne
|
|
||||||
break checkType
|
|
||||||
case "many":
|
|
||||||
fieldType = RelReverseMany
|
|
||||||
if tv := tags["rel_table"]; tv != "" {
|
|
||||||
fi.relTable = tv
|
|
||||||
} else if tv := tags["rel_through"]; tv != "" {
|
|
||||||
fi.relThrough = tv
|
|
||||||
}
|
|
||||||
break checkType
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("reverse only allow these value: one, many")
|
|
||||||
goto wrongTag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldType, err = getFieldType(addrField)
|
|
||||||
if err != nil {
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
if fieldType == TypeVarCharField {
|
|
||||||
switch tags["type"] {
|
|
||||||
case "char":
|
|
||||||
fieldType = TypeCharField
|
|
||||||
case "text":
|
|
||||||
fieldType = TypeTextField
|
|
||||||
case "json":
|
|
||||||
fieldType = TypeJSONField
|
|
||||||
case "jsonb":
|
|
||||||
fieldType = TypeJsonbField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fieldType == TypeFloatField && (digits != "" || decimals != "") {
|
|
||||||
fieldType = TypeDecimalField
|
|
||||||
}
|
|
||||||
if fieldType == TypeDateTimeField && tags["type"] == "date" {
|
|
||||||
fieldType = TypeDateField
|
|
||||||
}
|
|
||||||
if fieldType == TypeTimeField && tags["type"] == "time" {
|
|
||||||
fieldType = TypeTimeField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the rel and reverse type
|
|
||||||
// rel should Ptr
|
|
||||||
// reverse should slice []*struct
|
|
||||||
switch fieldType {
|
|
||||||
case RelForeignKey, RelOneToOne, RelReverseOne:
|
|
||||||
if field.Kind() != reflect.Ptr {
|
|
||||||
err = fmt.Errorf("rel/reverse:one field must be *%s", field.Type().Name())
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
case RelManyToMany, RelReverseMany:
|
|
||||||
if field.Kind() != reflect.Slice {
|
|
||||||
err = fmt.Errorf("rel/reverse:many field must be slice")
|
|
||||||
goto end
|
|
||||||
} else {
|
|
||||||
if field.Type().Elem().Kind() != reflect.Ptr {
|
|
||||||
err = fmt.Errorf("rel/reverse:many slice must be []*%s", field.Type().Elem().Name())
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fieldType&IsFieldType == 0 {
|
|
||||||
err = fmt.Errorf("wrong field type")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
|
|
||||||
fi.fieldType = fieldType
|
|
||||||
fi.name = sf.Name
|
|
||||||
fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
|
|
||||||
fi.addrValue = addrField
|
|
||||||
fi.sf = sf
|
|
||||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
|
||||||
|
|
||||||
fi.description = tags["description"]
|
|
||||||
fi.null = attrs["null"]
|
|
||||||
fi.index = attrs["index"]
|
|
||||||
fi.auto = attrs["auto"]
|
|
||||||
fi.pk = attrs["pk"]
|
|
||||||
fi.unique = attrs["unique"]
|
|
||||||
|
|
||||||
// Mark object property if there is attribute "default" in the orm configuration
|
|
||||||
if _, ok := tags["default"]; ok {
|
|
||||||
fi.colDefault = true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fieldType {
|
|
||||||
case RelManyToMany, RelReverseMany, RelReverseOne:
|
|
||||||
fi.null = false
|
|
||||||
fi.index = false
|
|
||||||
fi.auto = false
|
|
||||||
fi.pk = false
|
|
||||||
fi.unique = false
|
|
||||||
default:
|
|
||||||
fi.dbcol = true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fieldType {
|
|
||||||
case RelForeignKey, RelOneToOne, RelManyToMany:
|
|
||||||
fi.rel = true
|
|
||||||
if fieldType == RelOneToOne {
|
|
||||||
fi.unique = true
|
|
||||||
}
|
|
||||||
case RelReverseMany, RelReverseOne:
|
|
||||||
fi.reverse = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.rel && fi.dbcol {
|
|
||||||
switch onDelete {
|
|
||||||
case odCascade, odDoNothing:
|
|
||||||
case odSetDefault:
|
|
||||||
if !initial.Exist() {
|
|
||||||
err = errors.New("on_delete: set_default need set field a default value")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
case odSetNULL:
|
|
||||||
if !fi.null {
|
|
||||||
err = errors.New("on_delete: set_null need set field null")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if onDelete == "" {
|
|
||||||
onDelete = odCascade
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("on_delete value expected choice in `cascade,set_null,set_default,do_nothing`, unknown `%s`", onDelete)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fi.onDelete = onDelete
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fieldType {
|
|
||||||
case TypeBooleanField:
|
|
||||||
case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
|
|
||||||
if size != "" {
|
|
||||||
v, e := StrTo(size).Int32()
|
|
||||||
if e != nil {
|
|
||||||
err = fmt.Errorf("wrong size value `%s`", size)
|
|
||||||
} else {
|
|
||||||
fi.size = int(v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fi.size = 255
|
|
||||||
fi.toText = true
|
|
||||||
}
|
|
||||||
case TypeTextField:
|
|
||||||
fi.index = false
|
|
||||||
fi.unique = false
|
|
||||||
case TypeTimeField, TypeDateField, TypeDateTimeField:
|
|
||||||
if fieldType == TypeDateTimeField {
|
|
||||||
if precision != "" {
|
|
||||||
v, e := StrTo(precision).Int()
|
|
||||||
if e != nil {
|
|
||||||
err = fmt.Errorf("convert %s to int error:%v", precision, e)
|
|
||||||
} else {
|
|
||||||
fi.timePrecision = &v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if attrs["auto_now"] {
|
|
||||||
fi.autoNow = true
|
|
||||||
} else if attrs["auto_now_add"] {
|
|
||||||
fi.autoNowAdd = true
|
|
||||||
}
|
|
||||||
case TypeFloatField:
|
|
||||||
case TypeDecimalField:
|
|
||||||
d1 := digits
|
|
||||||
d2 := decimals
|
|
||||||
v1, er1 := StrTo(d1).Int8()
|
|
||||||
v2, er2 := StrTo(d2).Int8()
|
|
||||||
if er1 != nil || er2 != nil {
|
|
||||||
err = fmt.Errorf("wrong digits/decimals value %s/%s", d2, d1)
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
fi.digits = int(v1)
|
|
||||||
fi.decimals = int(v2)
|
|
||||||
default:
|
|
||||||
switch {
|
|
||||||
case fieldType&IsIntegerField > 0:
|
|
||||||
case fieldType&IsRelField > 0:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fieldType&IsIntegerField == 0 {
|
|
||||||
if fi.auto {
|
|
||||||
err = fmt.Errorf("non-integer type cannot set auto")
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.auto || fi.pk {
|
|
||||||
if fi.auto {
|
|
||||||
switch addrField.Elem().Kind() {
|
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
|
||||||
goto end
|
|
||||||
}
|
|
||||||
fi.pk = true
|
|
||||||
}
|
|
||||||
fi.null = false
|
|
||||||
fi.index = false
|
|
||||||
fi.unique = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.unique {
|
|
||||||
fi.index = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// can not set default for these type
|
|
||||||
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
|
||||||
initial.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
if initial.Exist() {
|
|
||||||
v := initial
|
|
||||||
switch fieldType {
|
|
||||||
case TypeBooleanField:
|
|
||||||
_, err = v.Bool()
|
|
||||||
case TypeFloatField, TypeDecimalField:
|
|
||||||
_, err = v.Float64()
|
|
||||||
case TypeBitField:
|
|
||||||
_, err = v.Int8()
|
|
||||||
case TypeSmallIntegerField:
|
|
||||||
_, err = v.Int16()
|
|
||||||
case TypeIntegerField:
|
|
||||||
_, err = v.Int32()
|
|
||||||
case TypeBigIntegerField:
|
|
||||||
_, err = v.Int64()
|
|
||||||
case TypePositiveBitField:
|
|
||||||
_, err = v.Uint8()
|
|
||||||
case TypePositiveSmallIntegerField:
|
|
||||||
_, err = v.Uint16()
|
|
||||||
case TypePositiveIntegerField:
|
|
||||||
_, err = v.Uint32()
|
|
||||||
case TypePositiveBigIntegerField:
|
|
||||||
_, err = v.Uint64()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
tag, tagValue = "default", tags["default"]
|
|
||||||
goto wrongTag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fi.initial = initial
|
|
||||||
end:
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
wrongTag:
|
|
||||||
return nil, fmt.Errorf("wrong tag format: `%s:\"%s\"`, %s", tag, tagValue, err)
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// single model info
|
|
||||||
type modelInfo struct {
|
|
||||||
manual bool
|
|
||||||
isThrough bool
|
|
||||||
pkg string
|
|
||||||
name string
|
|
||||||
fullName string
|
|
||||||
table string
|
|
||||||
model interface{}
|
|
||||||
fields *fields
|
|
||||||
addrField reflect.Value // store the original struct value
|
|
||||||
uniques []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// new model info
|
|
||||||
func newModelInfo(val reflect.Value) (mi *modelInfo) {
|
|
||||||
mi = &modelInfo{}
|
|
||||||
mi.fields = newFields()
|
|
||||||
ind := reflect.Indirect(val)
|
|
||||||
mi.addrField = val
|
|
||||||
mi.name = ind.Type().Name()
|
|
||||||
mi.fullName = getFullName(ind.Type())
|
|
||||||
addModelFields(mi, ind, "", []int{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// index: FieldByIndex returns the nested field corresponding to index
|
|
||||||
func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
fi *fieldInfo
|
|
||||||
sf reflect.StructField
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < ind.NumField(); i++ {
|
|
||||||
field := ind.Field(i)
|
|
||||||
sf = ind.Type().Field(i)
|
|
||||||
// if the field is unexported skip
|
|
||||||
if sf.PkgPath != "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// add anonymous struct fields
|
|
||||||
if sf.Anonymous {
|
|
||||||
addModelFields(mi, field, mName+"."+sf.Name, append(index, i))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err = newFieldInfo(mi, field, sf, mName)
|
|
||||||
if err == errSkipField {
|
|
||||||
err = nil
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// record current field index
|
|
||||||
fi.fieldIndex = append(fi.fieldIndex, index...)
|
|
||||||
fi.fieldIndex = append(fi.fieldIndex, i)
|
|
||||||
fi.mi = mi
|
|
||||||
fi.inModel = true
|
|
||||||
if !mi.fields.Add(fi) {
|
|
||||||
err = fmt.Errorf("duplicate column name: %s", fi.column)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if fi.pk {
|
|
||||||
if mi.fields.pk != nil {
|
|
||||||
err = fmt.Errorf("one model must have one pk field only")
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
mi.fields.pk = fi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// combine related model info to new model info.
|
|
||||||
// prepare for relation models query.
|
|
||||||
func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) {
|
|
||||||
mi = new(modelInfo)
|
|
||||||
mi.fields = newFields()
|
|
||||||
mi.table = m1.table + "_" + m2.table + "s"
|
|
||||||
mi.name = camelString(mi.table)
|
|
||||||
mi.fullName = m1.pkg + "." + mi.name
|
|
||||||
|
|
||||||
fa := new(fieldInfo) // pk
|
|
||||||
f1 := new(fieldInfo) // m1 table RelForeignKey
|
|
||||||
f2 := new(fieldInfo) // m2 table RelForeignKey
|
|
||||||
fa.fieldType = TypeBigIntegerField
|
|
||||||
fa.auto = true
|
|
||||||
fa.pk = true
|
|
||||||
fa.dbcol = true
|
|
||||||
fa.name = "Id"
|
|
||||||
fa.column = "id"
|
|
||||||
fa.fullName = mi.fullName + "." + fa.name
|
|
||||||
|
|
||||||
f1.dbcol = true
|
|
||||||
f2.dbcol = true
|
|
||||||
f1.fieldType = RelForeignKey
|
|
||||||
f2.fieldType = RelForeignKey
|
|
||||||
f1.name = camelString(m1.table)
|
|
||||||
f2.name = camelString(m2.table)
|
|
||||||
f1.fullName = mi.fullName + "." + f1.name
|
|
||||||
f2.fullName = mi.fullName + "." + f2.name
|
|
||||||
f1.column = m1.table + "_id"
|
|
||||||
f2.column = m2.table + "_id"
|
|
||||||
f1.rel = true
|
|
||||||
f2.rel = true
|
|
||||||
f1.relTable = m1.table
|
|
||||||
f2.relTable = m2.table
|
|
||||||
f1.relModelInfo = m1
|
|
||||||
f2.relModelInfo = m2
|
|
||||||
f1.mi = mi
|
|
||||||
f2.mi = mi
|
|
||||||
|
|
||||||
mi.fields.Add(fa)
|
|
||||||
mi.fields.Add(f1)
|
|
||||||
mi.fields.Add(f2)
|
|
||||||
mi.fields.pk = fa
|
|
||||||
|
|
||||||
mi.uniques = []string{f1.column, f2.column}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,243 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 1 is attr
|
|
||||||
// 2 is tag
|
|
||||||
var supportTag = map[string]int{
|
|
||||||
"-": 1,
|
|
||||||
"null": 1,
|
|
||||||
"index": 1,
|
|
||||||
"unique": 1,
|
|
||||||
"pk": 1,
|
|
||||||
"auto": 1,
|
|
||||||
"auto_now": 1,
|
|
||||||
"auto_now_add": 1,
|
|
||||||
"size": 2,
|
|
||||||
"column": 2,
|
|
||||||
"default": 2,
|
|
||||||
"rel": 2,
|
|
||||||
"reverse": 2,
|
|
||||||
"rel_table": 2,
|
|
||||||
"rel_through": 2,
|
|
||||||
"digits": 2,
|
|
||||||
"decimals": 2,
|
|
||||||
"on_delete": 2,
|
|
||||||
"type": 2,
|
|
||||||
"description": 2,
|
|
||||||
"precision": 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
// get reflect.Type name with package path.
|
|
||||||
func getFullName(typ reflect.Type) string {
|
|
||||||
return typ.PkgPath() + "." + typ.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTableName get struct table name.
|
|
||||||
// If the struct implement the TableName, then get the result as tablename
|
|
||||||
// else use the struct name which will apply snakeString.
|
|
||||||
func getTableName(val reflect.Value) string {
|
|
||||||
if fun := val.MethodByName("TableName"); fun.IsValid() {
|
|
||||||
vals := fun.Call([]reflect.Value{})
|
|
||||||
// has return and the first val is string
|
|
||||||
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
|
||||||
return vals[0].String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return snakeString(reflect.Indirect(val).Type().Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// get table engine, myisam or innodb.
|
|
||||||
func getTableEngine(val reflect.Value) string {
|
|
||||||
fun := val.MethodByName("TableEngine")
|
|
||||||
if fun.IsValid() {
|
|
||||||
vals := fun.Call([]reflect.Value{})
|
|
||||||
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
|
||||||
return vals[0].String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// get table index from method.
|
|
||||||
func getTableIndex(val reflect.Value) [][]string {
|
|
||||||
fun := val.MethodByName("TableIndex")
|
|
||||||
if fun.IsValid() {
|
|
||||||
vals := fun.Call([]reflect.Value{})
|
|
||||||
if len(vals) > 0 && vals[0].CanInterface() {
|
|
||||||
if d, ok := vals[0].Interface().([][]string); ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get table unique from method
|
|
||||||
func getTableUnique(val reflect.Value) [][]string {
|
|
||||||
fun := val.MethodByName("TableUnique")
|
|
||||||
if fun.IsValid() {
|
|
||||||
vals := fun.Call([]reflect.Value{})
|
|
||||||
if len(vals) > 0 && vals[0].CanInterface() {
|
|
||||||
if d, ok := vals[0].Interface().([][]string); ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get whether the table needs to be created for the database alias
|
|
||||||
func isApplicableTableForDB(val reflect.Value, db string) bool {
|
|
||||||
if !val.IsValid() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
fun := val.MethodByName("IsApplicableTableForDB")
|
|
||||||
if fun.IsValid() {
|
|
||||||
vals := fun.Call([]reflect.Value{reflect.ValueOf(db)})
|
|
||||||
if len(vals) > 0 && vals[0].Kind() == reflect.Bool {
|
|
||||||
return vals[0].Bool()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// get snaked column name
|
|
||||||
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
|
||||||
column := col
|
|
||||||
if col == "" {
|
|
||||||
column = nameStrategyMap[nameStrategy](sf.Name)
|
|
||||||
}
|
|
||||||
switch ft {
|
|
||||||
case RelForeignKey, RelOneToOne:
|
|
||||||
if len(col) == 0 {
|
|
||||||
column = column + "_id"
|
|
||||||
}
|
|
||||||
case RelManyToMany, RelReverseMany, RelReverseOne:
|
|
||||||
column = sf.Name
|
|
||||||
}
|
|
||||||
return column
|
|
||||||
}
|
|
||||||
|
|
||||||
// return field type as type constant from reflect.Value
|
|
||||||
func getFieldType(val reflect.Value) (ft int, err error) {
|
|
||||||
switch val.Type() {
|
|
||||||
case reflect.TypeOf(new(int8)):
|
|
||||||
ft = TypeBitField
|
|
||||||
case reflect.TypeOf(new(int16)):
|
|
||||||
ft = TypeSmallIntegerField
|
|
||||||
case reflect.TypeOf(new(int32)),
|
|
||||||
reflect.TypeOf(new(int)):
|
|
||||||
ft = TypeIntegerField
|
|
||||||
case reflect.TypeOf(new(int64)):
|
|
||||||
ft = TypeBigIntegerField
|
|
||||||
case reflect.TypeOf(new(uint8)):
|
|
||||||
ft = TypePositiveBitField
|
|
||||||
case reflect.TypeOf(new(uint16)):
|
|
||||||
ft = TypePositiveSmallIntegerField
|
|
||||||
case reflect.TypeOf(new(uint32)),
|
|
||||||
reflect.TypeOf(new(uint)):
|
|
||||||
ft = TypePositiveIntegerField
|
|
||||||
case reflect.TypeOf(new(uint64)):
|
|
||||||
ft = TypePositiveBigIntegerField
|
|
||||||
case reflect.TypeOf(new(float32)),
|
|
||||||
reflect.TypeOf(new(float64)):
|
|
||||||
ft = TypeFloatField
|
|
||||||
case reflect.TypeOf(new(bool)):
|
|
||||||
ft = TypeBooleanField
|
|
||||||
case reflect.TypeOf(new(string)):
|
|
||||||
ft = TypeVarCharField
|
|
||||||
case reflect.TypeOf(new(time.Time)):
|
|
||||||
ft = TypeDateTimeField
|
|
||||||
default:
|
|
||||||
elm := reflect.Indirect(val)
|
|
||||||
switch elm.Kind() {
|
|
||||||
case reflect.Int8:
|
|
||||||
ft = TypeBitField
|
|
||||||
case reflect.Int16:
|
|
||||||
ft = TypeSmallIntegerField
|
|
||||||
case reflect.Int32, reflect.Int:
|
|
||||||
ft = TypeIntegerField
|
|
||||||
case reflect.Int64:
|
|
||||||
ft = TypeBigIntegerField
|
|
||||||
case reflect.Uint8:
|
|
||||||
ft = TypePositiveBitField
|
|
||||||
case reflect.Uint16:
|
|
||||||
ft = TypePositiveSmallIntegerField
|
|
||||||
case reflect.Uint32, reflect.Uint:
|
|
||||||
ft = TypePositiveIntegerField
|
|
||||||
case reflect.Uint64:
|
|
||||||
ft = TypePositiveBigIntegerField
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
ft = TypeFloatField
|
|
||||||
case reflect.Bool:
|
|
||||||
ft = TypeBooleanField
|
|
||||||
case reflect.String:
|
|
||||||
ft = TypeVarCharField
|
|
||||||
default:
|
|
||||||
if elm.Interface() == nil {
|
|
||||||
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
|
|
||||||
}
|
|
||||||
switch elm.Interface().(type) {
|
|
||||||
case sql.NullInt64:
|
|
||||||
ft = TypeBigIntegerField
|
|
||||||
case sql.NullFloat64:
|
|
||||||
ft = TypeFloatField
|
|
||||||
case sql.NullBool:
|
|
||||||
ft = TypeBooleanField
|
|
||||||
case sql.NullString:
|
|
||||||
ft = TypeVarCharField
|
|
||||||
case time.Time:
|
|
||||||
ft = TypeDateTimeField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ft&IsFieldType == 0 {
|
|
||||||
err = fmt.Errorf("unsupport field type %s, may be miss setting tag", val)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse struct tag string
|
|
||||||
func parseStructTag(data string) (attrs map[string]bool, tags map[string]string) {
|
|
||||||
attrs = make(map[string]bool)
|
|
||||||
tags = make(map[string]string)
|
|
||||||
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
|
||||||
if v == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if t := strings.ToLower(v); supportTag[t] == 1 {
|
|
||||||
attrs[t] = true
|
|
||||||
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
|
||||||
name := t[:i]
|
|
||||||
if supportTag[name] == 2 {
|
|
||||||
v = v[i+1 : len(v)-1]
|
|
||||||
tags[name] = v
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DebugLog.Println("unsupport orm tag", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,661 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//go:build go1.8
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
// Package orm provide ORM for MySQL/PostgreSQL/sqlite
|
|
||||||
// Simple Usage
|
|
||||||
//
|
|
||||||
// package main
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "fmt"
|
|
||||||
// "github.com/beego/beego/v2/client/orm"
|
|
||||||
// _ "github.com/go-sql-driver/mysql" // import your used driver
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// // Model Struct
|
|
||||||
// type User struct {
|
|
||||||
// Id int `orm:"auto"`
|
|
||||||
// Name string `orm:"size(100)"`
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func init() {
|
|
||||||
// orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// o := orm.NewOrm()
|
|
||||||
// user := User{Name: "slene"}
|
|
||||||
// // insert
|
|
||||||
// id, err := o.Insert(&user)
|
|
||||||
// // update
|
|
||||||
// user.Name = "astaxie"
|
|
||||||
// num, err := o.Update(&user)
|
|
||||||
// // read one
|
|
||||||
// u := User{Id: user.Id}
|
|
||||||
// err = o.Read(&u)
|
|
||||||
// // delete
|
|
||||||
// num, err = o.Delete(&u)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// more docs: http://beego.vip/docs/mvc/model/overview.md
|
|
||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
|
||||||
"github.com/beego/beego/v2/client/orm/hints"
|
|
||||||
"github.com/beego/beego/v2/core/logs"
|
|
||||||
"github.com/beego/beego/v2/core/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DebugQueries define the debug
|
|
||||||
const (
|
|
||||||
DebugQueries = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// Define common vars
|
|
||||||
var (
|
|
||||||
Debug = false
|
|
||||||
DebugLog = NewLog(os.Stdout)
|
|
||||||
DefaultRowsLimit = -1
|
|
||||||
DefaultRelsDepth = 2
|
|
||||||
DefaultTimeLoc = time.Local
|
|
||||||
ErrTxDone = errors.New("<TxOrmer.Commit/Rollback> transaction already done")
|
|
||||||
ErrMultiRows = errors.New("<QuerySeter> return multi rows")
|
|
||||||
ErrNoRows = errors.New("<QuerySeter> no row found")
|
|
||||||
ErrStmtClosed = errors.New("<QuerySeter> stmt already closed")
|
|
||||||
ErrArgs = errors.New("<Ormer> args error may be empty")
|
|
||||||
ErrNotImplement = errors.New("have not implement")
|
|
||||||
|
|
||||||
ErrLastInsertIdUnavailable = errors.New("<Ormer> last insert id is unavailable")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Params stores the Params
|
|
||||||
type Params map[string]interface{}
|
|
||||||
|
|
||||||
// ParamsList stores paramslist
|
|
||||||
type ParamsList []interface{}
|
|
||||||
|
|
||||||
type ormBase struct {
|
|
||||||
alias *alias
|
|
||||||
db dbQuerier
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ DQL = new(ormBase)
|
|
||||||
_ DML = new(ormBase)
|
|
||||||
_ DriverGetter = new(ormBase)
|
|
||||||
)
|
|
||||||
|
|
||||||
// get model info and model reflect value
|
|
||||||
func (*ormBase) getMi(md interface{}) (mi *modelInfo) {
|
|
||||||
val := reflect.ValueOf(md)
|
|
||||||
ind := reflect.Indirect(val)
|
|
||||||
typ := ind.Type()
|
|
||||||
mi = getTypeMi(typ)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get need ptr model info and model reflect value
|
|
||||||
func (*ormBase) getPtrMiInd(md interface{}) (mi *modelInfo, ind reflect.Value) {
|
|
||||||
val := reflect.ValueOf(md)
|
|
||||||
ind = reflect.Indirect(val)
|
|
||||||
typ := ind.Type()
|
|
||||||
if val.Kind() != reflect.Ptr {
|
|
||||||
panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
|
||||||
}
|
|
||||||
mi = getTypeMi(typ)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTypeMi(mdTyp reflect.Type) *modelInfo {
|
|
||||||
name := getFullName(mdTyp)
|
|
||||||
if mi, ok := defaultModelCache.getByFullName(name); ok {
|
|
||||||
return mi
|
|
||||||
}
|
|
||||||
panic(fmt.Errorf("<Ormer> table: `%s` not found, make sure it was registered with `RegisterModel()`", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// get field info from model info by given field name
|
|
||||||
func (*ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
|
|
||||||
fi, ok := mi.fields.GetByAny(name)
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Errorf("<Ormer> cannot find field `%s` for model `%s`", name, mi.fullName))
|
|
||||||
}
|
|
||||||
return fi
|
|
||||||
}
|
|
||||||
|
|
||||||
// read data to model
|
|
||||||
func (o *ormBase) Read(md interface{}, cols ...string) error {
|
|
||||||
return o.ReadWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read data to model, like Read(), but use "SELECT FOR UPDATE" form
|
|
||||||
func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error {
|
|
||||||
return o.ReadForUpdateWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to read a row from the database, or insert one if it doesn't exist
|
|
||||||
func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
|
||||||
return o.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
|
||||||
cols = append([]string{col1}, cols...)
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
err := o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
|
|
||||||
if err == ErrNoRows {
|
|
||||||
// Create
|
|
||||||
id, err := o.InsertWithCtx(ctx, md)
|
|
||||||
return err == nil, id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex)
|
|
||||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
|
||||||
id = int64(vid.Uint())
|
|
||||||
} else if mi.fields.pk.rel {
|
|
||||||
return o.ReadOrCreateWithCtx(ctx, vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name)
|
|
||||||
} else {
|
|
||||||
id = vid.Int()
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert model data to database
|
|
||||||
func (o *ormBase) Insert(md interface{}) (int64, error) {
|
|
||||||
return o.InsertWithCtx(context.Background(), md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
o.setPk(mi, ind, id)
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// set auto pk field
|
|
||||||
func (*ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) {
|
|
||||||
if mi.fields.pk.auto {
|
|
||||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
|
||||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
|
||||||
} else {
|
|
||||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert some models to database
|
|
||||||
func (o *ormBase) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
|
||||||
return o.InsertMultiWithCtx(context.Background(), bulk, mds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
|
||||||
var cnt int64
|
|
||||||
|
|
||||||
sind := reflect.Indirect(reflect.ValueOf(mds))
|
|
||||||
|
|
||||||
switch sind.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if sind.Len() == 0 {
|
|
||||||
return cnt, ErrArgs
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return cnt, ErrArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
if bulk <= 1 {
|
|
||||||
for i := 0; i < sind.Len(); i++ {
|
|
||||||
ind := reflect.Indirect(sind.Index(i))
|
|
||||||
mi := o.getMi(ind.Interface())
|
|
||||||
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
|
|
||||||
if err != nil {
|
|
||||||
return cnt, err
|
|
||||||
}
|
|
||||||
|
|
||||||
o.setPk(mi, ind, id)
|
|
||||||
|
|
||||||
cnt++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mi := o.getMi(sind.Index(0).Interface())
|
|
||||||
return o.alias.DbBaser.InsertMulti(ctx, o.db, mi, sind, bulk, o.alias.TZ)
|
|
||||||
}
|
|
||||||
return cnt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertOrUpdate data to database
|
|
||||||
func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (int64, error) {
|
|
||||||
return o.InsertOrUpdateWithCtx(context.Background(), md, colConflictAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
id, err := o.alias.DbBaser.InsertOrUpdate(ctx, o.db, mi, ind, o.alias, colConflitAndArgs...)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
o.setPk(mi, ind, id)
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// update model to database.
|
|
||||||
// cols set the columns those want to update.
|
|
||||||
func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) {
|
|
||||||
return o.UpdateWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
return o.alias.DbBaser.Update(ctx, o.db, mi, ind, o.alias.TZ, cols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete model in database
|
|
||||||
// cols shows the delete conditions values read from. default is pk
|
|
||||||
func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) {
|
|
||||||
return o.DeleteWithCtx(context.Background(), md, cols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
num, err := o.alias.DbBaser.Delete(ctx, o.db, mi, ind, o.alias.TZ, cols)
|
|
||||||
return num, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a models to models queryer
|
|
||||||
func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
fi := o.getFieldInfo(mi, name)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case fi.fieldType == RelManyToMany:
|
|
||||||
case fi.fieldType == RelReverseMany && fi.reverseFieldInfo.mi.isThrough:
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("<Ormer.QueryM2M> model `%s` . name `%s` is not a m2m field", fi.name, mi.fullName))
|
|
||||||
}
|
|
||||||
|
|
||||||
return newQueryM2M(md, o, mi, fi, ind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
|
||||||
func (o *ormBase) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
|
|
||||||
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QueryM2M as replacement please.")
|
|
||||||
return o.QueryM2M(md, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// load related models to md model.
|
|
||||||
// args are limit, offset int and order string.
|
|
||||||
//
|
|
||||||
// example:
|
|
||||||
// orm.LoadRelated(post,"Tags")
|
|
||||||
// for _,tag := range post.Tags{...}
|
|
||||||
//
|
|
||||||
// make sure the relation is defined in model struct tags.
|
|
||||||
func (o *ormBase) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
|
||||||
return o.LoadRelatedWithCtx(context.Background(), md, name, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) LoadRelatedWithCtx(_ context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
|
||||||
_, fi, ind, qs := o.queryRelated(md, name)
|
|
||||||
|
|
||||||
var relDepth int
|
|
||||||
var limit, offset int64
|
|
||||||
var order string
|
|
||||||
|
|
||||||
kvs := utils.NewKVs(args...)
|
|
||||||
kvs.IfContains(hints.KeyRelDepth, func(value interface{}) {
|
|
||||||
if v, ok := value.(bool); ok {
|
|
||||||
if v {
|
|
||||||
relDepth = DefaultRelsDepth
|
|
||||||
}
|
|
||||||
} else if v, ok := value.(int); ok {
|
|
||||||
relDepth = v
|
|
||||||
}
|
|
||||||
}).IfContains(hints.KeyLimit, func(value interface{}) {
|
|
||||||
if v, ok := value.(int64); ok {
|
|
||||||
limit = v
|
|
||||||
}
|
|
||||||
}).IfContains(hints.KeyOffset, func(value interface{}) {
|
|
||||||
if v, ok := value.(int64); ok {
|
|
||||||
offset = v
|
|
||||||
}
|
|
||||||
}).IfContains(hints.KeyOrderBy, func(value interface{}) {
|
|
||||||
if v, ok := value.(string); ok {
|
|
||||||
order = v
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelOneToOne, RelForeignKey, RelReverseOne:
|
|
||||||
limit = 1
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
qs.limit = limit
|
|
||||||
qs.offset = offset
|
|
||||||
qs.relDepth = relDepth
|
|
||||||
|
|
||||||
if len(order) > 0 {
|
|
||||||
qs.orders = order_clause.ParseOrder(order)
|
|
||||||
}
|
|
||||||
|
|
||||||
find := ind.FieldByIndex(fi.fieldIndex)
|
|
||||||
|
|
||||||
var nums int64
|
|
||||||
var err error
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelOneToOne, RelForeignKey, RelReverseOne:
|
|
||||||
val := reflect.New(find.Type().Elem())
|
|
||||||
container := val.Interface()
|
|
||||||
err = qs.One(container)
|
|
||||||
if err == nil {
|
|
||||||
find.Set(val)
|
|
||||||
nums = 1
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
nums, err = qs.All(find.Addr().Interface())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nums, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get QuerySeter for related models to md model
|
|
||||||
func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, *querySet) {
|
|
||||||
mi, ind := o.getPtrMiInd(md)
|
|
||||||
fi := o.getFieldInfo(mi, name)
|
|
||||||
|
|
||||||
_, _, exist := getExistPk(mi, ind)
|
|
||||||
if !exist {
|
|
||||||
panic(ErrMissPK)
|
|
||||||
}
|
|
||||||
|
|
||||||
var qs *querySet
|
|
||||||
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelOneToOne, RelForeignKey, RelManyToMany:
|
|
||||||
if !fi.inModel {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
qs = o.getRelQs(md, mi, fi)
|
|
||||||
case RelReverseOne, RelReverseMany:
|
|
||||||
if !fi.inModel {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
qs = o.getReverseQs(md, mi, fi)
|
|
||||||
}
|
|
||||||
|
|
||||||
if qs == nil {
|
|
||||||
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available rel/reverse field", md, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mi, fi, ind, qs
|
|
||||||
}
|
|
||||||
|
|
||||||
// get reverse relation QuerySeter
|
|
||||||
func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelReverseOne, RelReverseMany:
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available reverse field", fi.name, mi.fullName))
|
|
||||||
}
|
|
||||||
|
|
||||||
var q *querySet
|
|
||||||
|
|
||||||
if fi.fieldType == RelReverseMany && fi.reverseFieldInfo.mi.isThrough {
|
|
||||||
q = newQuerySet(o, fi.relModelInfo).(*querySet)
|
|
||||||
q.cond = NewCondition().And(fi.reverseFieldInfoM2M.column+ExprSep+fi.reverseFieldInfo.column, md)
|
|
||||||
} else {
|
|
||||||
q = newQuerySet(o, fi.reverseFieldInfo.mi).(*querySet)
|
|
||||||
q.cond = NewCondition().And(fi.reverseFieldInfo.column, md)
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
// get relation QuerySeter
|
|
||||||
func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
|
||||||
switch fi.fieldType {
|
|
||||||
case RelOneToOne, RelForeignKey, RelManyToMany:
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available rel field", fi.name, mi.fullName))
|
|
||||||
}
|
|
||||||
|
|
||||||
q := newQuerySet(o, fi.relModelInfo).(*querySet)
|
|
||||||
q.cond = NewCondition()
|
|
||||||
|
|
||||||
if fi.fieldType == RelManyToMany {
|
|
||||||
q.cond = q.cond.And(fi.reverseFieldInfoM2M.column+ExprSep+fi.reverseFieldInfo.column, md)
|
|
||||||
} else {
|
|
||||||
q.cond = q.cond.And(fi.reverseFieldInfo.column, md)
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
// return a QuerySeter for table operations.
|
|
||||||
// table name can be string or struct.
|
|
||||||
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
|
|
||||||
func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
|
||||||
var name string
|
|
||||||
if table, ok := ptrStructOrTableName.(string); ok {
|
|
||||||
name = nameStrategyMap[defaultNameStrategy](table)
|
|
||||||
if mi, ok := defaultModelCache.get(name); ok {
|
|
||||||
qs = newQuerySet(o, mi)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
|
||||||
if mi, ok := defaultModelCache.getByFullName(name); ok {
|
|
||||||
qs = newQuerySet(o, mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if qs == nil {
|
|
||||||
panic(fmt.Errorf("<Ormer.QueryTable> table name: `%s` not exists", name))
|
|
||||||
}
|
|
||||||
return qs
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this method is deprecated, context parameter will not take effect.
|
|
||||||
func (o *ormBase) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) {
|
|
||||||
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QuerySeter as replacement please.")
|
|
||||||
return o.QueryTable(ptrStructOrTableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// return a raw query seter for raw sql string.
|
|
||||||
func (o *ormBase) Raw(query string, args ...interface{}) RawSeter {
|
|
||||||
return o.RawWithCtx(context.Background(), query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ormBase) RawWithCtx(_ context.Context, query string, args ...interface{}) RawSeter {
|
|
||||||
return newRawSet(o, query, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// return current using database Driver
|
|
||||||
func (o *ormBase) Driver() Driver {
|
|
||||||
return driver(o.alias.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// return sql.DBStats for current database
|
|
||||||
func (o *ormBase) DBStats() *sql.DBStats {
|
|
||||||
if o.alias != nil && o.alias.DB != nil {
|
|
||||||
stats := o.alias.DB.DB.Stats()
|
|
||||||
return &stats
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type orm struct {
|
|
||||||
ormBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Ormer = new(orm)
|
|
||||||
|
|
||||||
func (o *orm) Begin() (TxOrmer, error) {
|
|
||||||
return o.BeginWithCtx(context.Background())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
|
||||||
return o.BeginWithCtxAndOpts(ctx, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
|
||||||
return o.BeginWithCtxAndOpts(context.Background(), opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
|
||||||
tx, err := o.db.(txer).BeginTx(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_txOrm := &txOrm{
|
|
||||||
ormBase: ormBase{
|
|
||||||
alias: o.alias,
|
|
||||||
db: &TxDB{tx: tx},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
_txOrm.db = newDbQueryLog(o.alias, _txOrm.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
var taskTxOrm TxOrmer = _txOrm
|
|
||||||
return taskTxOrm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return o.DoTxWithCtx(context.Background(), task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return o.DoTxWithCtxAndOpts(ctx, nil, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return o.DoTxWithCtxAndOpts(context.Background(), opts, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
return doTxTemplate(ctx, o, opts, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTxTemplate(ctx context.Context, o TxBeginner, opts *sql.TxOptions,
|
|
||||||
task func(ctx context.Context, txOrm TxOrmer) error) error {
|
|
||||||
_txOrm, err := o.BeginWithCtxAndOpts(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
panicked := true
|
|
||||||
defer func() {
|
|
||||||
if panicked || err != nil {
|
|
||||||
e := _txOrm.Rollback()
|
|
||||||
if e != nil {
|
|
||||||
logs.Error("rollback transaction failed: %v,%v", e, panicked)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
e := _txOrm.Commit()
|
|
||||||
if e != nil {
|
|
||||||
logs.Error("commit transaction failed: %v,%v", e, panicked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
taskTxOrm := _txOrm
|
|
||||||
err = task(ctx, taskTxOrm)
|
|
||||||
panicked = false
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type txOrm struct {
|
|
||||||
ormBase
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ TxOrmer = new(txOrm)
|
|
||||||
|
|
||||||
func (t *txOrm) Commit() error {
|
|
||||||
return t.db.(txEnder).Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *txOrm) Rollback() error {
|
|
||||||
return t.db.(txEnder).Rollback()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *txOrm) RollbackUnlessCommit() error {
|
|
||||||
return t.db.(txEnder).RollbackUnlessCommit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOrm create new orm
|
|
||||||
func NewOrm() Ormer {
|
|
||||||
BootStrap() // execute only once
|
|
||||||
return NewOrmUsingDB(`default`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOrmUsingDB create new orm with the name
|
|
||||||
func NewOrmUsingDB(aliasName string) Ormer {
|
|
||||||
if al, ok := dataBaseCache.get(aliasName); ok {
|
|
||||||
return newDBWithAlias(al)
|
|
||||||
}
|
|
||||||
panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", aliasName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOrmWithDB create a new ormer object with specify *sql.DB for query
|
|
||||||
func NewOrmWithDB(driverName, aliasName string, db *sql.DB, params ...DBOption) (Ormer, error) {
|
|
||||||
al, err := newAliasWithDb(aliasName, driverName, db, params...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newDBWithAlias(al), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDBWithAlias(al *alias) Ormer {
|
|
||||||
o := new(orm)
|
|
||||||
o.alias = al
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
o.db = newDbQueryLog(al, al.DB)
|
|
||||||
} else {
|
|
||||||
o.db = al.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(globalFilterChains) > 0 {
|
|
||||||
return NewFilterOrmDecorator(o, globalFilterChains...)
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue