Browse Source

Merge core package back into the main repository and split into serval sub packages. (#1543)

Fix test

Improve fmt

update go.mod

Move core as a sub package

Reviewed-on: #1543
tags/v1.0.0
Lunny Xiao 1 month ago
parent
commit
bf25a77bca
87 changed files with 4456 additions and 3198 deletions
  1. +2
    -0
      .gitignore
  2. +5
    -3
      cache_test.go
  3. +99
    -0
      caches/cache.go
  4. +9
    -11
      caches/cache_lru.go
  5. +3
    -3
      caches/cache_lru_test.go
  6. +2
    -4
      caches/cache_memory_store.go
  7. +1
    -1
      caches/cache_memory_store_test.go
  8. +7
    -0
      convert.go
  9. +229
    -0
      core/db.go
  10. +684
    -0
      core/db_test.go
  11. +14
    -0
      core/error.go
  12. +338
    -0
      core/rows.go
  13. +66
    -0
      core/scan.go
  14. +166
    -0
      core/stmt.go
  15. +153
    -0
      core/tx.go
  16. +410
    -0
      dialects/dialect.go
  17. +31
    -0
      dialects/driver.go
  18. +95
    -0
      dialects/filter.go
  19. +39
    -0
      dialects/filter_test.go
  20. +59
    -58
      dialects/mssql.go
  21. +4
    -6
      dialects/mssql_test.go
  22. +68
    -67
      dialects/mysql.go
  23. +52
    -51
      dialects/oracle.go
  24. +87
    -86
      dialects/postgres.go
  25. +9
    -10
      dialects/postgres_test.go
  26. +47
    -46
      dialects/sqlite3.go
  27. +1
    -1
      dialects/sqlite3_test.go
  28. +1
    -1
      doc.go
  29. +0
    -2295
      docs/images/cache_design.graffle
  30. BIN
      docs/images/cache_design.png
  31. +90
    -85
      engine.go
  32. +6
    -6
      engine_cond.go
  33. +9
    -7
      engine_group.go
  34. +9
    -7
      engine_table.go
  35. +3
    -2
      examples/cache/cache.go
  36. +3
    -2
      examples/cache_gorountine/cache_goroutine.go
  37. +1
    -1
      go.mod
  38. +0
    -2
      go.sum
  39. +2
    -22
      helpers.go
  40. +20
    -16
      interface.go
  41. +64
    -34
      log/logger.go
  42. +6
    -8
      log/syslogger.go
  43. +2
    -2
      migrate/migrate.go
  44. +258
    -0
      names/mapper.go
  45. +49
    -0
      names/mapper_test.go
  46. +10
    -3
      names/table_name.go
  47. +41
    -13
      names/table_name_test.go
  48. +1
    -1
      rows.go
  49. +117
    -0
      schemas/column.go
  50. +72
    -0
      schemas/index.go
  51. +30
    -0
      schemas/pk.go
  52. +36
    -0
      schemas/pk_test.go
  53. +156
    -0
      schemas/table.go
  54. +111
    -0
      schemas/table_test.go
  55. +325
    -0
      schemas/type.go
  56. +37
    -36
      session.go
  57. +4
    -4
      session_cols.go
  58. +2
    -2
      session_cols_test.go
  59. +41
    -41
      session_convert.go
  60. +13
    -12
      session_delete.go
  61. +5
    -4
      session_delete_test.go
  62. +5
    -5
      session_exist.go
  63. +13
    -12
      session_find.go
  64. +3
    -3
      session_find_test.go
  65. +7
    -6
      session_get.go
  66. +4
    -4
      session_get_test.go
  67. +11
    -11
      session_insert.go
  68. +14
    -14
      session_pk_test.go
  69. +4
    -3
      session_query.go
  70. +5
    -5
      session_query_test.go
  71. +1
    -1
      session_raw.go
  72. +30
    -30
      session_schema.go
  73. +1
    -1
      session_schema_test.go
  74. +2
    -2
      session_tx_test.go
  75. +13
    -12
      session_update.go
  76. +3
    -3
      session_update_test.go
  77. +40
    -35
      statement.go
  78. +3
    -3
      statement_args.go
  79. +6
    -6
      statement_test.go
  80. +32
    -11
      tag.go
  81. +7
    -7
      tag_extends_test.go
  82. +3
    -3
      tag_id_test.go
  83. +2
    -2
      tag_test.go
  84. +3
    -3
      types.go
  85. +6
    -6
      types_test.go
  86. +16
    -41
      xorm.go
  87. +18
    -16
      xorm_test.go

+ 2
- 0
.gitignore View File

@@ -32,3 +32,5 @@ xorm.test
test.db.sql

.idea/

*coverage.out

+ 5
- 3
cache_test.go View File

@@ -8,6 +8,8 @@ import (
"testing"
"time"

"xorm.io/xorm/caches"

"github.com/stretchr/testify/assert"
)

@@ -21,7 +23,7 @@ func TestCacheFind(t *testing.T) {
}

oldCacher := testEngine.GetDefaultCacher()
cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)
cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)
testEngine.SetDefaultCacher(cacher)

assert.NoError(t, testEngine.Sync2(new(MailBox)))
@@ -96,7 +98,7 @@ func TestCacheFind2(t *testing.T) {
}

oldCacher := testEngine.GetDefaultCacher()
cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)
cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)
testEngine.SetDefaultCacher(cacher)

assert.NoError(t, testEngine.Sync2(new(MailBox2)))
@@ -147,7 +149,7 @@ func TestCacheGet(t *testing.T) {
}

oldCacher := testEngine.GetDefaultCacher()
cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)
cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)
testEngine.SetDefaultCacher(cacher)

assert.NoError(t, testEngine.Sync2(new(MailBox3)))


+ 99
- 0
caches/cache.go View File

@@ -0,0 +1,99 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package caches

import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"strings"
"time"

"xorm.io/xorm/schemas"
)

const (
// CacheExpired is default cache expired time
CacheExpired = 60 * time.Minute
// CacheMaxMemory is not use now
CacheMaxMemory = 256
// CacheGcInterval represents interval time to clear all expired nodes
CacheGcInterval = 10 * time.Minute
// CacheGcMaxRemoved represents max nodes removed when gc
CacheGcMaxRemoved = 20
)

// list all the errors
var (
ErrCacheMiss = errors.New("xorm/cache: key not found")
ErrNotStored = errors.New("xorm/cache: not stored")
// ErrNotExist record does not exist error
ErrNotExist = errors.New("Record does not exist")
)

// CacheStore is a interface to store cache
type CacheStore interface {
// key is primary key or composite primary key
// value is struct's pointer
// key format : <tablename>-p-<pk1>-<pk2>...
Put(key string, value interface{}) error
Get(key string) (interface{}, error)
Del(key string) error
}

// Cacher is an interface to provide cache
// id format : u-<pk1>-<pk2>...
type Cacher interface {
GetIds(tableName, sql string) interface{}
GetBean(tableName string, id string) interface{}
PutIds(tableName, sql string, ids interface{})
PutBean(tableName string, id string, obj interface{})
DelIds(tableName, sql string)
DelBean(tableName string, id string)
ClearIds(tableName string)
ClearBeans(tableName string)
}

func encodeIds(ids []schemas.PK) (string, error) {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
err := enc.Encode(ids)

return buf.String(), err
}

func decodeIds(s string) ([]schemas.PK, error) {
pks := make([]schemas.PK, 0)

dec := gob.NewDecoder(strings.NewReader(s))
err := dec.Decode(&pks)

return pks, err
}

// GetCacheSql returns cacher PKs via SQL
func GetCacheSql(m Cacher, tableName, sql string, args interface{}) ([]schemas.PK, error) {
bytes := m.GetIds(tableName, GenSqlKey(sql, args))
if bytes == nil {
return nil, errors.New("Not Exist")
}
return decodeIds(bytes.(string))
}

// PutCacheSql puts cacher SQL and PKs
func PutCacheSql(m Cacher, ids []schemas.PK, tableName, sql string, args interface{}) error {
bytes, err := encodeIds(ids)
if err != nil {
return err
}
m.PutIds(tableName, GenSqlKey(sql, args), bytes)
return nil
}

// GenSqlKey generates cache key
func GenSqlKey(sql string, args interface{}) string {
return fmt.Sprintf("%v-%v", sql, args)
}

cache_lru.go → caches/cache_lru.go View File

@@ -2,15 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xorm
package caches

import (
"container/list"
"fmt"
"sync"
"time"

"xorm.io/core"
)

// LRUCacher implments cache object facilities
@@ -19,7 +17,7 @@ type LRUCacher struct {
sqlList *list.List
idIndex map[string]map[string]*list.Element
sqlIndex map[string]map[string]*list.Element
store core.CacheStore
store CacheStore
mutex sync.Mutex
MaxElementSize int
Expired time.Duration
@@ -27,15 +25,15 @@ type LRUCacher struct {
}

// NewLRUCacher creates a cacher
func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher {
func NewLRUCacher(store CacheStore, maxElementSize int) *LRUCacher {
return NewLRUCacher2(store, 3600*time.Second, maxElementSize)
}

// NewLRUCacher2 creates a cache include different params
func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher {
func NewLRUCacher2(store CacheStore, expired time.Duration, maxElementSize int) *LRUCacher {
cacher := &LRUCacher{store: store, idList: list.New(),
sqlList: list.New(), Expired: expired,
GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize,
GcInterval: CacheGcInterval, MaxElementSize: maxElementSize,
sqlIndex: make(map[string]map[string]*list.Element),
idIndex: make(map[string]map[string]*list.Element),
}
@@ -57,7 +55,7 @@ func (m *LRUCacher) GC() {
defer m.mutex.Unlock()
var removedNum int
for e := m.idList.Front(); e != nil; {
if removedNum <= core.CacheGcMaxRemoved &&
if removedNum <= CacheGcMaxRemoved &&
time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
removedNum++
next := e.Next()
@@ -71,7 +69,7 @@ func (m *LRUCacher) GC() {

removedNum = 0
for e := m.sqlList.Front(); e != nil; {
if removedNum <= core.CacheGcMaxRemoved &&
if removedNum <= CacheGcMaxRemoved &&
time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
removedNum++
next := e.Next()
@@ -268,11 +266,11 @@ type sqlNode struct {
}

func genSQLKey(sql string, args interface{}) string {
return fmt.Sprintf("%v-%v", sql, args)
return fmt.Sprintf("%s-%v", sql, args)
}

func genID(prefix string, id string) string {
return fmt.Sprintf("%v-%v", prefix, id)
return fmt.Sprintf("%s-%s", prefix, id)
}

func newIDNode(tbName string, id string) *idNode {

cache_lru_test.go → caches/cache_lru_test.go View File

@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xorm
package caches

import (
"testing"

"github.com/stretchr/testify/assert"
"xorm.io/core"
"xorm.io/xorm/schemas"
)

func TestLRUCache(t *testing.T) {
@@ -20,7 +20,7 @@ func TestLRUCache(t *testing.T) {
cacher := NewLRUCacher(store, 10000)

tableName := "cache_object1"
pks := []core.PK{
pks := []schemas.PK{
{1},
{2},
}

cache_memory_store.go → caches/cache_memory_store.go View File

@@ -2,15 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xorm
package caches

import (
"sync"

"xorm.io/core"
)

var _ core.CacheStore = NewMemoryStore()
var _ CacheStore = NewMemoryStore()

// MemoryStore represents in-memory store
type MemoryStore struct {

cache_memory_store_test.go → caches/cache_memory_store_test.go View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xorm
package caches

import (
"testing"

+ 7
- 0
convert.go View File

@@ -346,3 +346,10 @@ func asBool(bs []byte) (bool, error) {
}
return strconv.ParseBool(string(bs))
}

// Conversion is an interface. A type implements Conversion will according
// the custom method to fill into database and retrieve from database.
type Conversion interface {
FromDB([]byte) error
ToDB() ([]byte, error)
}

+ 229
- 0
core/db.go View File

@@ -0,0 +1,229 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"regexp"
"sync"

"xorm.io/xorm/names"
)

var (
// DefaultCacheSize sets the default cache size
DefaultCacheSize = 200
)

func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
vv := reflect.ValueOf(mp)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return "", []interface{}{}, ErrNoMapPointer
}

args := make([]interface{}, 0, len(vv.Elem().MapKeys()))
var err error
query = re.ReplaceAllStringFunc(query, func(src string) string {
v := vv.Elem().MapIndex(reflect.ValueOf(src[1:]))
if !v.IsValid() {
err = fmt.Errorf("map key %s is missing", src[1:])
} else {
args = append(args, v.Interface())
}
return "?"
})

return query, args, err
}

func StructToSlice(query string, st interface{}) (string, []interface{}, error) {
vv := reflect.ValueOf(st)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
return "", []interface{}{}, ErrNoStructPointer
}

args := make([]interface{}, 0)
var err error
query = re.ReplaceAllStringFunc(query, func(src string) string {
fv := vv.Elem().FieldByName(src[1:]).Interface()
if v, ok := fv.(driver.Valuer); ok {
var value driver.Value
value, err = v.Value()
if err != nil {
return "?"
}
args = append(args, value)
} else {
args = append(args, fv)
}
return "?"
})
if err != nil {
return "", []interface{}{}, err
}
return query, args, nil
}

type cacheStruct struct {
value reflect.Value
idx int
}

// DB is a wrap of sql.DB with extra contents
type DB struct {
*sql.DB
Mapper names.Mapper
reflectCache map[reflect.Type]*cacheStruct
reflectCacheMutex sync.RWMutex
}

// Open opens a database
func Open(driverName, dataSourceName string) (*DB, error) {
db, err := sql.Open(driverName, dataSourceName)
if err != nil {
return nil, err
}
return &DB{
DB: db,
Mapper: names.NewCacheMapper(&names.SnakeMapper{}),
reflectCache: make(map[reflect.Type]*cacheStruct),
}, nil
}

// FromDB creates a DB from a sql.DB
func FromDB(db *sql.DB) *DB {
return &DB{
DB: db,
Mapper: names.NewCacheMapper(&names.SnakeMapper{}),
reflectCache: make(map[reflect.Type]*cacheStruct),
}
}

func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
db.reflectCacheMutex.Lock()
defer db.reflectCacheMutex.Unlock()
cs, ok := db.reflectCache[typ]
if !ok || cs.idx+1 > DefaultCacheSize-1 {
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
db.reflectCache[typ] = cs
} else {
cs.idx = cs.idx + 1
}
return cs.value.Index(cs.idx).Addr()
}

// QueryContext overwrites sql.DB.QueryContext
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
rows, err := db.DB.QueryContext(ctx, query, args...)
if err != nil {
if rows != nil {
rows.Close()
}
return nil, err
}
return &Rows{rows, db}, nil
}

// Query overwrites sql.DB.Query
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
return db.QueryContext(context.Background(), query, args...)
}

// QueryMapContext executes query with parameters via map and context
func (db *DB) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) {
query, args, err := MapToSlice(query, mp)
if err != nil {
return nil, err
}
return db.QueryContext(ctx, query, args...)
}

// QueryMap executes query with parameters via map
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
return db.QueryMapContext(context.Background(), query, mp)
}

func (db *DB) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) {
query, args, err := StructToSlice(query, st)
if err != nil {
return nil, err
}
return db.QueryContext(ctx, query, args...)
}

func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) {
return db.QueryStructContext(context.Background(), query, st)
}

func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row {
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return &Row{nil, err}
}
return &Row{rows, nil}
}

func (db *DB) QueryRow(query string, args ...interface{}) *Row {
return db.QueryRowContext(context.Background(), query, args...)
}

func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row {
query, args, err := MapToSlice(query, mp)
if err != nil {
return &Row{nil, err}
}
return db.QueryRowContext(ctx, query, args...)
}

func (db *DB) QueryRowMap(query string, mp interface{}) *Row {
return db.QueryRowMapContext(context.Background(), query, mp)
}

func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row {
query, args, err := StructToSlice(query, st)
if err != nil {
return &Row{nil, err}
}
return db.QueryRowContext(ctx, query, args...)
}

func (db *DB) QueryRowStruct(query string, st interface{}) *Row {
return db.QueryRowStructContext(context.Background(), query, st)
}

var (
re = regexp.MustCompile(`[?](\w+)`)
)

// ExecMapContext exec map with context.Context
// insert into (name) values (?)
// insert into (name) values (?name)
func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) {
query, args, err := MapToSlice(query, mp)
if err != nil {
return nil, err
}
return db.DB.ExecContext(ctx, query, args...)
}

func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) {
return db.ExecMapContext(context.Background(), query, mp)
}

func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) {
query, args, err := StructToSlice(query, st)
if err != nil {
return nil, err
}
return db.DB.ExecContext(ctx, query, args...)
}

func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) {
return db.ExecStructContext(context.Background(), query, st)
}

+ 684
- 0
core/db_test.go View File

@@ -0,0 +1,684 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import (
"errors"
"flag"
"os"
"testing"
"time"

_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
"xorm.io/xorm/names"
)

var (
dbtype = flag.String("dbtype", "mysql", "database type")
dbConn = flag.String("dbConn", "root:@/core_test?charset=utf8", "database connect string")
createTableSql string
)

func TestMain(m *testing.M) {
flag.Parse()

switch *dbtype {
case "sqlite3":
createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " +
"`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);"
case "mysql":
fallthrough
default:
createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` TEXT NULL, " +
"`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);"
}

exitCode := m.Run()

os.Exit(exitCode)
}

func testOpen() (*DB, error) {
switch *dbtype {
case "sqlite3":
os.Remove("./test.db")
return Open("sqlite3", "./test.db")
case "mysql":
return Open("mysql", *dbConn)
default:
panic("no db type")
}
}

func BenchmarkOriQuery(b *testing.B) {
b.StopTimer()
db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

for rows.Next() {
var Id int64
var Name, Title, Alias, NickName string
var Age float32
var Created NullTime
err = rows.Scan(&Id, &Name, &Title, &Age, &Alias, &NickName, &Created)
if err != nil {
b.Error(err)
}
//fmt.Println(Id, Name, Title, Age, Alias, NickName)
}
rows.Close()
}
}

type User struct {
Id int64
Name string
Title string
Age float32
Alias string
NickName string
Created NullTime
}

func BenchmarkStructQuery(b *testing.B) {
b.StopTimer()

db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

for rows.Next() {
var user User
err = rows.ScanStructByIndex(&user)
if err != nil {
b.Error(err)
}
if user.Name != "xlw" {
b.Log(user)
b.Error(errors.New("name should be xlw"))
}
}
rows.Close()
}
}

func BenchmarkStruct2Query(b *testing.B) {
b.StopTimer()

db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

db.Mapper = names.NewCacheMapper(&names.SnakeMapper{})
b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

for rows.Next() {
var user User
err = rows.ScanStructByName(&user)
if err != nil {
b.Error(err)
}
if user.Name != "xlw" {
b.Log(user)
b.Error(errors.New("name should be xlw"))
}
}
rows.Close()
}
}

func BenchmarkSliceInterfaceQuery(b *testing.B) {
b.StopTimer()

db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

cols, err := rows.Columns()
if err != nil {
b.Error(err)
}

for rows.Next() {
slice := make([]interface{}, len(cols))
err = rows.ScanSlice(&slice)
if err != nil {
b.Error(err)
}
b.Log(slice)
switch slice[1].(type) {
case *string:
if *slice[1].(*string) != "xlw" {
b.Error(errors.New("name should be xlw"))
}
case []byte:
if string(slice[1].([]byte)) != "xlw" {
b.Error(errors.New("name should be xlw"))
}
}
}

rows.Close()
}
}

/*func BenchmarkSliceBytesQuery(b *testing.B) {
b.StopTimer()
os.Remove("./test.db")
db, err := Open("sqlite3", "./test.db")
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

cols, err := rows.Columns()
if err != nil {
b.Error(err)
}

for rows.Next() {
slice := make([][]byte, len(cols))
err = rows.ScanSlice(&slice)
if err != nil {
b.Error(err)
}
if string(slice[1]) != "xlw" {
fmt.Println(slice)
b.Error(errors.New("name should be xlw"))
}
}

rows.Close()
}
}
*/

func BenchmarkSliceStringQuery(b *testing.B) {
b.StopTimer()
db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (name, title, age, alias, nick_name, created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

cols, err := rows.Columns()
if err != nil {
b.Error(err)
}

for rows.Next() {
slice := make([]*string, len(cols))
err = rows.ScanSlice(&slice)
if err != nil {
b.Error(err)
}
if (*slice[1]) != "xlw" {
b.Log(slice)
b.Error(errors.New("name should be xlw"))
}
}

rows.Close()
}
}

func BenchmarkMapInterfaceQuery(b *testing.B) {
b.StopTimer()

db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

for rows.Next() {
m := make(map[string]interface{})
err = rows.ScanMap(&m)
if err != nil {
b.Error(err)
}
switch m["name"].(type) {
case string:
if m["name"].(string) != "xlw" {
b.Log(m)
b.Error(errors.New("name should be xlw"))
}
case []byte:
if string(m["name"].([]byte)) != "xlw" {
b.Log(m)
b.Error(errors.New("name should be xlw"))
}
}
}

rows.Close()
}
}

/*func BenchmarkMapBytesQuery(b *testing.B) {
b.StopTimer()
os.Remove("./test.db")
db, err := Open("sqlite3", "./test.db")
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

for rows.Next() {
m := make(map[string][]byte)
err = rows.ScanMap(&m)
if err != nil {
b.Error(err)
}
if string(m["name"]) != "xlw" {
fmt.Println(m)
b.Error(errors.New("name should be xlw"))
}
}

rows.Close()
}
}
*/
/*
func BenchmarkMapStringQuery(b *testing.B) {
b.StopTimer()
os.Remove("./test.db")
db, err := Open("sqlite3", "./test.db")
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

for i := 0; i < 50; i++ {
_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}

b.StartTimer()

for i := 0; i < b.N; i++ {
rows, err := db.Query("select * from user")
if err != nil {
b.Error(err)
}

for rows.Next() {
m := make(map[string]string)
err = rows.ScanMap(&m)
if err != nil {
b.Error(err)
}
if m["name"] != "xlw" {
fmt.Println(m)
b.Error(errors.New("name should be xlw"))
}
}

rows.Close()
}
}*/

func BenchmarkExec(b *testing.B) {
b.StopTimer()

db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

b.StartTimer()

for i := 0; i < b.N; i++ {
_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
if err != nil {
b.Error(err)
}
}
}

func BenchmarkExecMap(b *testing.B) {
b.StopTimer()

db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

b.StartTimer()

mp := map[string]interface{}{
"name": "xlw",
"title": "tester",
"age": 1.2,
"alias": "lunny",
"nick_name": "lunny xiao",
"created": time.Now(),
}

for i := 0; i < b.N; i++ {
_, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name, created) "+
"values (?name,?title,?age,?alias,?nick_name,?created)",
&mp)
if err != nil {
b.Error(err)
}
}
}

func TestExecMap(t *testing.T) {
db, err := testOpen()
if err != nil {
t.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
t.Error(err)
}

mp := map[string]interface{}{
"name": "xlw",
"title": "tester",
"age": 1.2,
"alias": "lunny",
"nick_name": "lunny xiao",
"created": time.Now(),
}

_, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name,created) "+
"values (?name,?title,?age,?alias,?nick_name,?created)",
&mp)
if err != nil {
t.Error(err)
}

rows, err := db.Query("select * from user")
if err != nil {
t.Error(err)
}

for rows.Next() {
var user User
err = rows.ScanStructByName(&user)
if err != nil {
t.Error(err)
}
t.Log("--", user)
}
}

func TestExecStruct(t *testing.T) {
db, err := testOpen()
if err != nil {
t.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
t.Error(err)
}

user := User{Name: "xlw",
Title: "tester",
Age: 1.2,
Alias: "lunny",
NickName: "lunny xiao",
Created: NullTime(time.Now()),
}

_, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+
"values (?Name,?Title,?Age,?Alias,?NickName,?Created)",
&user)
if err != nil {
t.Error(err)
}

rows, err := db.QueryStruct("select * from user where `name` = ?Name", &user)
if err != nil {
t.Error(err)
}

for rows.Next() {
var user User
err = rows.ScanStructByName(&user)
if err != nil {
t.Error(err)
}
t.Log("1--", user)
}
}

func BenchmarkExecStruct(b *testing.B) {
b.StopTimer()
db, err := testOpen()
if err != nil {
b.Error(err)
}
defer db.Close()

_, err = db.Exec(createTableSql)
if err != nil {
b.Error(err)
}

b.StartTimer()

user := User{Name: "xlw",
Title: "tester",
Age: 1.2,
Alias: "lunny",
NickName: "lunny xiao",
Created: NullTime(time.Now()),
}

for i := 0; i < b.N; i++ {
_, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+
"values (?Name,?Title,?Age,?Alias,?NickName,?Created)",
&user)
if err != nil {
b.Error(err)
}
}
}

+ 14
- 0
core/error.go View File

@@ -0,0 +1,14 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import "errors"

var (
// ErrNoMapPointer represents error when no map pointer
ErrNoMapPointer = errors.New("mp should be a map's pointer")
// ErrNoStructPointer represents error when no struct pointer
ErrNoStructPointer = errors.New("mp should be a struct's pointer")
)

+ 338
- 0
core/rows.go View File

@@ -0,0 +1,338 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import (
"database/sql"
"errors"
"reflect"
"sync"
)

type Rows struct {
*sql.Rows
db *DB
}

func (rs *Rows) ToMapString() ([]map[string]string, error) {
cols, err := rs.Columns()
if err != nil {
return nil, err
}

var results = make([]map[string]string, 0, 10)
for rs.Next() {
var record = make(map[string]string, len(cols))
err = rs.ScanMap(&record)
if err != nil {
return nil, err
}
results = append(results, record)
}
return results, nil
}

// scan data to a struct's pointer according field index
func (rs *Rows) ScanStructByIndex(dest ...interface{}) error {
if len(dest) == 0 {
return errors.New("at least one struct")
}

vvvs := make([]reflect.Value, len(dest))
for i, s := range dest {
vv := reflect.ValueOf(s)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
return errors.New("dest should be a struct's pointer")
}

vvvs[i] = vv.Elem()
}

cols, err := rs.Columns()
if err != nil {
return err
}
newDest := make([]interface{}, len(cols))

var i = 0
for _, vvv := range vvvs {
for j := 0; j < vvv.NumField(); j++ {
newDest[i] = vvv.Field(j).Addr().Interface()
i = i + 1
}
}

return rs.Rows.Scan(newDest...)
}

var (
fieldCache = make(map[reflect.Type]map[string]int)
fieldCacheMutex sync.RWMutex
)

func fieldByName(v reflect.Value, name string) reflect.Value {
t := v.Type()
fieldCacheMutex.RLock()
cache, ok := fieldCache[t]
fieldCacheMutex.RUnlock()
if !ok {
cache = make(map[string]int)
for i := 0; i < v.NumField(); i++ {
cache[t.Field(i).Name] = i
}
fieldCacheMutex.Lock()
fieldCache[t] = cache
fieldCacheMutex.Unlock()
}

if i, ok := cache[name]; ok {
return v.Field(i)
}

return reflect.Zero(t)
}

// scan data to a struct's pointer according field name
func (rs *Rows) ScanStructByName(dest interface{}) error {
vv := reflect.ValueOf(dest)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
return errors.New("dest should be a struct's pointer")
}

cols, err := rs.Columns()
if err != nil {
return err
}

newDest := make([]interface{}, len(cols))
var v EmptyScanner
for j, name := range cols {
f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name))
if f.IsValid() {
newDest[j] = f.Addr().Interface()
} else {
newDest[j] = &v
}
}

return rs.Rows.Scan(newDest...)
}

// scan data to a slice's pointer, slice's length should equal to columns' number
func (rs *Rows) ScanSlice(dest interface{}) error {
vv := reflect.ValueOf(dest)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Slice {
return errors.New("dest should be a slice's pointer")
}

vvv := vv.Elem()
cols, err := rs.Columns()
if err != nil {
return err
}

newDest := make([]interface{}, len(cols))

for j := 0; j < len(cols); j++ {
if j >= vvv.Len() {
newDest[j] = reflect.New(vvv.Type().Elem()).Interface()
} else {
newDest[j] = vvv.Index(j).Addr().Interface()
}
}

err = rs.Rows.Scan(newDest...)
if err != nil {
return err
}

srcLen := vvv.Len()
for i := srcLen; i < len(cols); i++ {
vvv = reflect.Append(vvv, reflect.ValueOf(newDest[i]).Elem())
}
return nil
}

// scan data to a map's pointer
func (rs *Rows) ScanMap(dest interface{}) error {
vv := reflect.ValueOf(dest)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return errors.New("dest should be a map's pointer")
}

cols, err := rs.Columns()
if err != nil {
return err
}

newDest := make([]interface{}, len(cols))
vvv := vv.Elem()

for i := range cols {
newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface()
}

err = rs.Rows.Scan(newDest...)
if err != nil {
return err
}

for i, name := range cols {
vname := reflect.ValueOf(name)
vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem())
}

return nil
}

type Row struct {
rows *Rows
// One of these two will be non-nil:
err error // deferred error for easy chaining
}

// ErrorRow return an error row
func ErrorRow(err error) *Row {
return &Row{
err: err,
}
}

// NewRow from rows
func NewRow(rows *Rows, err error) *Row {
return &Row{rows, err}
}

func (row *Row) Columns() ([]string, error) {
if row.err != nil {
return nil, row.err
}
return row.rows.Columns()
}

func (row *Row) Scan(dest ...interface{}) error {
if row.err != nil {
return row.err
}
defer row.rows.Close()

for _, dp := range dest {
if _, ok := dp.(*sql.RawBytes); ok {
return errors.New("sql: RawBytes isn't allowed on Row.Scan")
}
}

if !row.rows.Next() {
if err := row.rows.Err(); err != nil {
return err
}
return sql.ErrNoRows
}
err := row.rows.Scan(dest...)
if err != nil {
return err
}
// Make sure the query can be processed to completion with no errors.
return row.rows.Close()
}

func (row *Row) ScanStructByName(dest interface{}) error {
if row.err != nil {
return row.err
}
defer row.rows.Close()

if !row.rows.Next() {
if err := row.rows.Err(); err != nil {
return err
}
return sql.ErrNoRows
}
err := row.rows.ScanStructByName(dest)
if err != nil {
return err
}
// Make sure the query can be processed to completion with no errors.
return row.rows.Close()
}

func (row *Row) ScanStructByIndex(dest interface{}) error {
if row.err != nil {
return row.err
}
defer row.rows.Close()

if !row.rows.Next() {
if err := row.rows.Err(); err != nil {
return err
}
return sql.ErrNoRows
}
err := row.rows.ScanStructByIndex(dest)
if err != nil {
return err
}
// Make sure the query can be processed to completion with no errors.
return row.rows.Close()
}

// scan data to a slice's pointer, slice's length should equal to columns' number
func (row *Row) ScanSlice(dest interface{}) error {
if row.err != nil {
return row.err
}
defer row.rows.Close()

if !row.rows.Next() {
if err := row.rows.Err(); err != nil {
return err
}
return sql.ErrNoRows
}
err := row.rows.ScanSlice(dest)
if err != nil {
return err
}

// Make sure the query can be processed to completion with no errors.
return row.rows.Close()
}

// scan data to a map's pointer
func (row *Row) ScanMap(dest interface{}) error {
if row.err != nil {
return row.err
}
defer row.rows.Close()

if !row.rows.Next() {
if err := row.rows.Err(); err != nil {
return err
}
return sql.ErrNoRows
}
err := row.rows.ScanMap(dest)
if err != nil {
return err
}

// Make sure the query can be processed to completion with no errors.
return row.rows.Close()
}

func (row *Row) ToMapString() (map[string]string, error) {
cols, err := row.Columns()
if err != nil {
return nil, err
}

var record = make(map[string]string, len(cols))
err = row.ScanMap(&record)
if err != nil {
return nil, err
}

return record, nil
}

+ 66
- 0
core/scan.go View File

@@ -0,0 +1,66 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import (
"database/sql/driver"
"fmt"
"time"
)

type NullTime time.Time

var (
_ driver.Valuer = NullTime{}
)

func (ns *NullTime) Scan(value interface{}) error {
if value == nil {
return nil
}
return convertTime(ns, value)
}

// Value implements the driver Valuer interface.
func (ns NullTime) Value() (driver.Value, error) {
if (time.Time)(ns).IsZero() {
return nil, nil
}
return (time.Time)(ns).Format("2006-01-02 15:04:05"), nil
}

func convertTime(dest *NullTime, src interface{}) error {
// Common cases, without reflect.
switch s := src.(type) {
case string:
t, err := time.Parse("2006-01-02 15:04:05", s)
if err != nil {
return err
}
*dest = NullTime(t)
return nil
case []uint8:
t, err := time.Parse("2006-01-02 15:04:05", string(s))
if err != nil {
return err
}
*dest = NullTime(t)
return nil
case time.Time:
*dest = NullTime(s)
return nil
case nil:
default:
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)
}
return nil
}

type EmptyScanner struct {
}

func (EmptyScanner) Scan(src interface{}) error {
return nil
}

+ 166
- 0
core/stmt.go View File

@@ -0,0 +1,166 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import (
"context"
"database/sql"
"errors"
"reflect"
)

// Stmt reprents a stmt objects
type Stmt struct {
*sql.Stmt
db *DB
names map[string]int
}

func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
names := make(map[string]int)
var i int
query = re.ReplaceAllStringFunc(query, func(src string) string {
names[src[1:]] = i
i += 1
return "?"
})

stmt, err := db.DB.PrepareContext(ctx, query)
if err != nil {
return nil, err
}
return &Stmt{stmt, db, names}, nil
}

func (db *DB) Prepare(query string) (*Stmt, error) {
return db.PrepareContext(context.Background(), query)
}

func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, error) {
vv := reflect.ValueOf(mp)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return nil, errors.New("mp should be a map's pointer")
}

args := make([]interface{}, len(s.names))
for k, i := range s.names {
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface()
}
return s.Stmt.ExecContext(ctx, args...)
}

func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) {
return s.ExecMapContext(context.Background(), mp)
}

func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Result, error) {
vv := reflect.ValueOf(st)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
return nil, errors.New("mp should be a map's pointer")
}

args := make([]interface{}, len(s.names))
for k, i := range s.names {
args[i] = vv.Elem().FieldByName(k).Interface()
}
return s.Stmt.ExecContext(ctx, args...)
}

func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) {
return s.ExecStructContext(context.Background(), st)
}

func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) {
rows, err := s.Stmt.QueryContext(ctx, args...)
if err != nil {
return nil, err
}
return &Rows{rows, s.db}, nil
}

func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
return s.QueryContext(context.Background(), args...)
}

func (s *Stmt) QueryMapContext(ctx context.Context, mp interface{}) (*Rows, error) {
vv := reflect.ValueOf(mp)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return nil, errors.New("mp should be a map's pointer")
}

args := make([]interface{}, len(s.names))
for k, i := range s.names {
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface()
}

return s.QueryContext(ctx, args...)
}

func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) {
return s.QueryMapContext(context.Background(), mp)
}

func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, error) {
vv := reflect.ValueOf(st)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
return nil, errors.New("mp should be a map's pointer")
}

args := make([]interface{}, len(s.names))
for k, i := range s.names {
args[i] = vv.Elem().FieldByName(k).Interface()
}

return s.Query(args...)
}

func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) {
return s.QueryStructContext(context.Background(), st)
}

func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row {
rows, err := s.QueryContext(ctx, args...)
return &Row{rows, err}
}

func (s *Stmt) QueryRow(args ...interface{}) *Row {
return s.QueryRowContext(context.Background(), args...)
}

func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row {
vv := reflect.ValueOf(mp)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return &Row{nil, errors.New("mp should be a map's pointer")}
}

args := make([]interface{}, len(s.names))
for k, i := range s.names {
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface()
}

return s.QueryRowContext(ctx, args...)
}

func (s *Stmt) QueryRowMap(mp interface{}) *Row {
return s.QueryRowMapContext(context.Background(), mp)
}

func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row {
vv := reflect.ValueOf(st)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
return &Row{nil, errors.New("st should be a struct's pointer")}
}

args := make([]interface{}, len(s.names))
for k, i := range s.names {
args[i] = vv.Elem().FieldByName(k).Interface()
}

return s.QueryRowContext(ctx, args...)
}

func (s *Stmt) QueryRowStruct(st interface{}) *Row {
return s.QueryRowStructContext(context.Background(), st)
}

+ 153
- 0
core/tx.go View File

@@ -0,0 +1,153 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package core

import (
"context"
"database/sql"
)

type Tx struct {
*sql.Tx
db *DB
}

func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
tx, err := db.DB.BeginTx(ctx, opts)
if err != nil {
return nil, err
}
return &Tx{tx, db}, nil
}

func (db *DB) Begin() (*Tx, error) {
tx, err := db.DB.Begin()
if err != nil {
return nil, err
}
return &Tx{tx, db}, nil
}

func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
names := make(map[string]int)
var i int
query = re.ReplaceAllStringFunc(query, func(src string) string {
names[src[1:]] = i
i += 1
return "?"
})

stmt, err := tx.Tx.PrepareContext(ctx, query)
if err != nil {
return nil, err
}
return &Stmt{stmt, tx.db, names}, nil
}

func (tx *Tx) Prepare(query string) (*Stmt, error) {
return tx.PrepareContext(context.Background(), query)
}

func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt {
stmt.Stmt = tx.Tx.StmtContext(ctx, stmt.Stmt)
return stmt
}

func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
return tx.StmtContext(context.Background(), stmt)
}

func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) {
query, args, err := MapToSlice(query, mp)
if err != nil {
return nil, err
}
return tx.Tx.ExecContext(ctx, query, args...)
}

func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) {
return tx.ExecMapContext(context.Background(), query, mp)
}

func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) {
query, args, err := StructToSlice(query, st)
if err != nil {
return nil, err
}
return tx.Tx.ExecContext(ctx, query, args...)
}

func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) {
return tx.ExecStructContext(context.Background(), query, st)
}

func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
rows, err := tx.Tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
return &Rows{rows, tx.db}, nil
}

func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
return tx.QueryContext(context.Background(), query, args...)
}

func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) {
query, args, err := MapToSlice(query, mp)
if err != nil {
return nil, err
}
return tx.QueryContext(ctx, query, args...)
}

func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) {
return tx.QueryMapContext(context.Background(), query, mp)
}

func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) {
query, args, err := StructToSlice(query, st)
if err != nil {
return nil, err
}
return tx.QueryContext(ctx, query, args...)
}

func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) {
return tx.QueryStructContext(context.Background(), query, st)
}

func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row {
rows, err := tx.QueryContext(ctx, query, args...)
return &Row{rows, err}
}

func (tx *Tx) QueryRow(query string, args ...interface{}) *Row {
return tx.QueryRowContext(context.Background(), query, args...)
}

func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row {
query, args, err := MapToSlice(query, mp)
if err != nil {
return &Row{nil, err}
}
return tx.QueryRowContext(ctx, query, args...)
}

func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row {
return tx.QueryRowMapContext(context.Background(), query, mp)
}

func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row {
query, args, err := StructToSlice(query, st)
if err != nil {
return &Row{nil, err}
}
return tx.QueryRowContext(ctx, query, args...)
}

func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row {
return tx.QueryRowStructContext(context.Background(), query, st)
}

+ 410
- 0
dialects/dialect.go View File

@@ -0,0 +1,410 @@
// Copyright 2019 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dialects

import (
"fmt"
"strings"
"time"

"xorm.io/xorm/core"
"xorm.io/xorm/log"
"xorm.io/xorm/schemas"
)

type DBType string

type URI struct {
DBType DBType
Proto string
Host string
Port string
DBName string
User string
Passwd string
Charset string
Laddr string
Raddr string
Timeout time.Duration
Schema string
}

// a dialect is a driver's wrapper
type Dialect interface {
SetLogger(logger log.Logger)
Init(*core.DB, *URI, string, string) error
URI() *URI
DB() *core.DB
DBType() DBType
SQLType(*schemas.Column) string
FormatBytes(b []byte) string

DriverName() string
DataSourceName() string

IsReserved(string) bool
Quote(string) string

AndStr() string
OrStr() string
EqStr() string
RollBackStr() string
AutoIncrStr() string

SupportInsertMany() bool
SupportEngine() bool
SupportCharset() bool
SupportDropIfExists() bool
IndexOnTable() bool
ShowCreateNull() bool

IndexCheckSQL(tableName, idxName string) (string, []interface{})
TableCheckSQL(tableName string) (string, []interface{})

IsColumnExist(tableName string, colName string) (bool, error)

CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string
DropTableSQL(tableName string) string
CreateIndexSQL(tableName string, index *schemas.Index) string
DropIndexSQL(tableName string, index *schemas.Index) string

ModifyColumnSQL(tableName string, col *schemas.Column) string

ForUpdateSQL(query string) string

// CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error
// MustDropTable(tableName string) error

GetColumns(tableName string) ([]string, map[string]*schemas.Column, error)
GetTables() ([]*schemas.Table, error)
GetIndexes(tableName string) (map[string]*schemas.Index, error)

Filters() []Filter
SetParams(params map[string]string)
}

func OpenDialect(dialect Dialect) (*core.DB, error) {
return core.Open(dialect.DriverName(), dialect.DataSourceName())
}

// Base represents a basic dialect and all real dialects could embed this struct
type Base struct {
db *core.DB
dialect Dialect
driverName string
dataSourceName string
logger log.Logger
uri *URI
}

// String generate column description string according dialect
func String(d Dialect, col *schemas.Column) string {
sql := d.Quote(col.Name) + " "

sql += d.SQLType(col) + " "

if col.IsPrimaryKey {
sql += "PRIMARY KEY "
if col.IsAutoIncrement {
sql += d.AutoIncrStr() + " "
}
}

if col.Default != "" {
sql += "DEFAULT " + col.Default + " "
}

if d.ShowCreateNull() {
if col.Nullable {
sql += "NULL "
} else {
sql += "NOT NULL "
}
}

return sql
}

// StringNoPk generate column description string according dialect without primary keys
func StringNoPk(d Dialect, col *schemas.Column) string {
sql := d.Quote(col.Name) + " "

sql += d.SQLType(col) + " "

if col.Default != "" {
sql += "DEFAULT " + col.Default + " "
}

if d.ShowCreateNull() {
if col.Nullable {
sql += "NULL "
} else {