Query interface #1965
48
convert/interface.go
Normal file
48
convert/interface.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2021 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 convert
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Interface2Interface(userLocation *time.Location, v interface{}) (interface{}, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch vv := v.(type) {
|
||||
case *int64:
|
||||
return *vv, nil
|
||||
case *int8:
|
||||
return *vv, nil
|
||||
case *sql.NullString:
|
||||
return vv.String, nil
|
||||
case *sql.RawBytes:
|
||||
if len([]byte(*vv)) > 0 {
|
||||
return []byte(*vv), nil
|
||||
}
|
||||
return nil, nil
|
||||
case *sql.NullInt32:
|
||||
return vv.Int32, nil
|
||||
case *sql.NullInt64:
|
||||
return vv.Int64, nil
|
||||
case *sql.NullFloat64:
|
||||
return vv.Float64, nil
|
||||
case *sql.NullBool:
|
||||
if vv.Valid {
|
||||
return vv.Bool, nil
|
||||
}
|
||||
return nil, nil
|
||||
case *sql.NullTime:
|
||||
if vv.Valid {
|
||||
return vv.Time.In(userLocation).Format("2006-01-02 15:04:05"), nil
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
return "", fmt.Errorf("convert assign string unsupported type: %#v", vv)
|
||||
}
|
||||
}
|
30
convert/time.go
Normal file
30
convert/time.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2021 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 convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// String2Time converts a string to time with original location
|
||||
func String2Time(s string, originalLocation *time.Location, convertedLocation *time.Location) (*time.Time, error) {
|
||||
if len(s) == 19 {
|
||||
dt, err := time.ParseInLocation("2006-01-02 15:04:05", s, originalLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dt = dt.In(convertedLocation)
|
||||
return &dt, nil
|
||||
} else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' {
|
||||
dt, err := time.ParseInLocation("2006-01-02T15:04:05Z", s, originalLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dt = dt.In(convertedLocation)
|
||||
return &dt, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported convertion from %s to time", s)
|
||||
}
|
|
@ -5,12 +5,24 @@
|
|||
package dialects
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm/core"
|
||||
)
|
||||
|
||||
// ScanContext represents a context when Scan
|
||||
type ScanContext struct {
|
||||
DBLocation *time.Location
|
||||
UserLocation *time.Location
|
||||
}
|
||||
|
||||
// Driver represents a database driver
|
||||
type Driver interface {
|
||||
Parse(string, string) (*URI, error)
|
||||
GenScanResult(string) (interface{}, error) // according given column type generating a suitable scan interface
|
||||
Scan(*ScanContext, *core.Rows, []*sql.ColumnType, ...interface{}) error
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -59,3 +71,9 @@ func OpenDialect(driverName, connstr string) (Dialect, error) {
|
|||
|
||||
return dialect, nil
|
||||
}
|
||||
|
||||
type baseDriver struct{}
|
||||
|
||||
func (b *baseDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, v ...interface{}) error {
|
||||
return rows.Scan(v...)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package dialects
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -624,6 +625,7 @@ func (db *mssql) Filters() []Filter {
|
|||
}
|
||||
|
||||
type odbcDriver struct {
|
||||
baseDriver
|
||||
}
|
||||
|
||||
func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
||||
|
@ -652,3 +654,26 @@ func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
|||
}
|
||||
return &URI{DBName: dbName, DBType: schemas.MSSQL}, nil
|
||||
}
|
||||
|
||||
func (p *odbcDriver) GenScanResult(colType string) (interface{}, error) {
|
||||
switch colType {
|
||||
case "VARCHAR", "TEXT", "CHAR", "NVARCHAR", "NCHAR", "NTEXT":
|
||||
fallthrough
|
||||
case "DATE", "DATETIME", "DATETIME2", "TIME":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "FLOAT", "REAL":
|
||||
var s sql.NullFloat64
|
||||
return &s, nil
|
||||
case "BIGINT", "DATETIMEOFFSET":
|
||||
var s sql.NullInt64
|
||||
return &s, nil
|
||||
case "TINYINT", "SMALLINT", "INT":
|
||||
var s sql.NullInt32
|
||||
return &s, nil
|
||||
|
||||
default:
|
||||
var r sql.RawBytes
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package dialects
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm/convert"
|
||||
"xorm.io/xorm/core"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
@ -630,7 +632,124 @@ func (db *mysql) Filters() []Filter {
|
|||
return []Filter{}
|
||||
}
|
||||
|
||||
type mysqlDriver struct {
|
||||
}
|
||||
|
||||
func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||
`\/(?P<dbname>.*?)` + // /dbname
|
||||
`(?:\?(?P<params>[^\?]*))?$`) // [?param1=value1¶mN=valueN]
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
// tlsConfigRegister := make(map[string]*tls.Config)
|
||||
names := dsnPattern.SubexpNames()
|
||||
|
||||
uri := &URI{DBType: schemas.MYSQL}
|
||||
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
uri.DBName = match
|
||||
case "params":
|
||||
if len(match) > 0 {
|
||||
kvs := strings.Split(match, "&")
|
||||
for _, kv := range kvs {
|
||||
splits := strings.Split(kv, "=")
|
||||
if len(splits) == 2 {
|
||||
switch splits[0] {
|
||||
case "charset":
|
||||
uri.Charset = splits[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return uri, nil
|
||||
}
|
||||
|
||||
func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) {
|
||||
switch colType {
|
||||
case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "BIGINT":
|
||||
var s sql.NullInt64
|
||||
return &s, nil
|
||||
case "TINYINT", "SMALLINT", "MEDIUMINT", "INT":
|
||||
var s sql.NullInt32
|
||||
return &s, nil
|
||||
case "FLOAT", "REAL", "DOUBLE PRECISION":
|
||||
var s sql.NullFloat64
|
||||
return &s, nil
|
||||
case "DECIMAL", "NUMERIC":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "DATETIME":
|
||||
var s sql.NullTime
|
||||
return &s, nil
|
||||
case "BIT":
|
||||
var s sql.RawBytes
|
||||
return &s, nil
|
||||
case "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB":
|
||||
var r sql.RawBytes
|
||||
return &r, nil
|
||||
default:
|
||||
var r sql.RawBytes
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *mysqlDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, scanResults ...interface{}) error {
|
||||
var v2 = make([]interface{}, 0, len(scanResults))
|
||||
var turnBackIdxes = make([]int, 0, 5)
|
||||
for i, vv := range scanResults {
|
||||
switch vv.(type) {
|
||||
case *time.Time:
|
||||
v2 = append(v2, &sql.NullString{})
|
||||
turnBackIdxes = append(turnBackIdxes, i)
|
||||
case *sql.NullTime:
|
||||
v2 = append(v2, &sql.NullString{})
|
||||
turnBackIdxes = append(turnBackIdxes, i)
|
||||
default:
|
||||
v2 = append(v2, scanResults[i])
|
||||
}
|
||||
}
|
||||
if err := rows.Scan(v2...); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range turnBackIdxes {
|
||||
switch t := scanResults[i].(type) {
|
||||
case *time.Time:
|
||||
var s = *(v2[i].(*sql.NullString))
|
||||
if !s.Valid {
|
||||
break
|
||||
}
|
||||
dt, err := convert.String2Time(s.String, ctx.DBLocation, ctx.UserLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = *dt
|
||||
case *sql.NullTime:
|
||||
var s = *(v2[i].(*sql.NullString))
|
||||
if !s.Valid {
|
||||
break
|
||||
}
|
||||
dt, err := convert.String2Time(s.String, ctx.DBLocation, ctx.UserLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Time = *dt
|
||||
t.Valid = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type mymysqlDriver struct {
|
||||
mysqlDriver
|
||||
}
|
||||
|
||||
func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
||||
|
@ -681,41 +800,3 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
|||
|
||||
return uri, nil
|
||||
}
|
||||
|
||||
type mysqlDriver struct {
|
||||
}
|
||||
|
||||
func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
||||
dsnPattern := regexp.MustCompile(
|
||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||
`\/(?P<dbname>.*?)` + // /dbname
|
||||
`(?:\?(?P<params>[^\?]*))?$`) // [?param1=value1¶mN=valueN]
|
||||
matches := dsnPattern.FindStringSubmatch(dataSourceName)
|
||||
// tlsConfigRegister := make(map[string]*tls.Config)
|
||||
names := dsnPattern.SubexpNames()
|
||||
|
||||
uri := &URI{DBType: schemas.MYSQL}
|
||||
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "dbname":
|
||||
uri.DBName = match
|
||||
case "params":
|
||||
if len(match) > 0 {
|
||||
kvs := strings.Split(match, "&")
|
||||
for _, kv := range kvs {
|
||||
splits := strings.Split(kv, "=")
|
||||
if len(splits) == 2 {
|
||||
switch splits[0] {
|
||||
case "charset":
|
||||
uri.Charset = splits[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return uri, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package dialects
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
@ -823,6 +824,7 @@ func (db *oracle) Filters() []Filter {
|
|||
}
|
||||
|
||||
type godrorDriver struct {
|
||||
baseDriver
|
||||
}
|
||||
|
||||
func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
||||
|
@ -848,7 +850,28 @@ func (cfg *godrorDriver) Parse(driverName, dataSourceName string) (*URI, error)
|
|||
return db, nil
|
||||
}
|
||||
|
||||
func (p *godrorDriver) GenScanResult(colType string) (interface{}, error) {
|
||||
switch colType {
|
||||
case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "NUMBER":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "DATE":
|
||||
var s sql.NullTime
|
||||
return &s, nil
|
||||
case "BLOB":
|
||||
var r sql.RawBytes
|
||||
return &r, nil
|
||||
default:
|
||||
var r sql.RawBytes
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
||||
type oci8Driver struct {
|
||||
godrorDriver
|
||||
}
|
||||
|
||||
// dataSourceName=user/password@ipv4:port/dbname
|
||||
|
|
|
@ -6,6 +6,7 @@ package dialects
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -1298,6 +1299,7 @@ func (db *postgres) Filters() []Filter {
|
|||
}
|
||||
|
||||
type pqDriver struct {
|
||||
baseDriver
|
||||
}
|
||||
|
||||
type values map[string]string
|
||||
|
@ -1374,6 +1376,36 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*URI, error) {
|
|||
return db, nil
|
||||
}
|
||||
|
||||
func (p *pqDriver) GenScanResult(colType string) (interface{}, error) {
|
||||
switch colType {
|
||||
case "VARCHAR", "TEXT":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "BIGINT":
|
||||
var s sql.NullInt64
|
||||
return &s, nil
|
||||
case "TINYINT", "INT", "INT8", "INT4":
|
||||
var s sql.NullInt32
|
||||
return &s, nil
|
||||
case "FLOAT", "FLOAT4":
|
||||
var s sql.NullFloat64
|
||||
return &s, nil
|
||||
case "DATETIME", "TIMESTAMP":
|
||||
var s sql.NullTime
|
||||
return &s, nil
|
||||
case "BIT":
|
||||
var s sql.RawBytes
|
||||
return &s, nil
|
||||
case "BOOL":
|
||||
var s sql.NullBool
|
||||
return &s, nil
|
||||
default:
|
||||
fmt.Printf("unknow postgres database type: %v\n", colType)
|
||||
var r sql.RawBytes
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
||||
type pqDriverPgx struct {
|
||||
pqDriver
|
||||
}
|
||||
|
|
|
@ -540,6 +540,7 @@ func (db *sqlite3) Filters() []Filter {
|
|||
}
|
||||
|
||||
type sqlite3Driver struct {
|
||||
baseDriver
|
||||
}
|
||||
|
||||
func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) {
|
||||
|
@ -549,3 +550,29 @@ func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) {
|
|||
|
||||
return &URI{DBType: schemas.SQLITE, DBName: dataSourceName}, nil
|
||||
}
|
||||
|
||||
func (p *sqlite3Driver) GenScanResult(colType string) (interface{}, error) {
|
||||
switch colType {
|
||||
case "TEXT":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "INTEGER":
|
||||
var s sql.NullInt64
|
||||
return &s, nil
|
||||
case "DATETIME":
|
||||
var s sql.NullTime
|
||||
return &s, nil
|
||||
case "REAL":
|
||||
var s sql.NullFloat64
|
||||
return &s, nil
|
||||
case "NUMERIC":
|
||||
var s sql.NullString
|
||||
return &s, nil
|
||||
case "BLOB":
|
||||
var s sql.RawBytes
|
||||
return &s, nil
|
||||
default:
|
||||
var r sql.NullString
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ type Engine struct {
|
|||
cacherMgr *caches.Manager
|
||||
defaultContext context.Context
|
||||
dialect dialects.Dialect
|
||||
driver dialects.Driver
|
||||
engineGroup *EngineGroup
|
||||
logger log.ContextLogger
|
||||
tagParser *tags.Parser
|
||||
|
@ -72,6 +73,7 @@ func newEngine(driverName, dataSourceName string, dialect dialects.Dialect, db *
|
|||
|
||||
engine := &Engine{
|
||||
dialect: dialect,
|
||||
driver: dialects.QueryDriver(driverName),
|
||||
TZLocation: time.Local,
|
||||
defaultContext: context.Background(),
|
||||
cacherMgr: cacherMgr,
|
||||
|
|
|
@ -107,6 +107,16 @@ func toFloat64(i interface{}) float64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func toBool(i interface{}) bool {
|
||||
switch t := i.(type) {
|
||||
case int32:
|
||||
return t > 0
|
||||
case bool:
|
||||
return t
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestQueryInterface(t *testing.T) {
|
||||
assert.NoError(t, PrepareEngine())
|
||||
|
||||
|
@ -132,10 +142,10 @@ func TestQueryInterface(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(records))
|
||||
assert.Equal(t, 5, len(records[0]))
|
||||
assert.EqualValues(t, 1, toInt64(records[0]["id"]))
|
||||
assert.Equal(t, "hi", toString(records[0]["msg"]))
|
||||
assert.EqualValues(t, 28, toInt64(records[0]["age"]))
|
||||
assert.EqualValues(t, 1.5, toFloat64(records[0]["money"]))
|
||||
assert.EqualValues(t, int64(1), records[0]["id"])
|
||||
assert.Equal(t, "hi", records[0]["msg"])
|
||||
assert.EqualValues(t, 28, records[0]["age"])
|
||||
assert.EqualValues(t, 1.5, records[0]["money"])
|
||||
}
|
||||
|
||||
func TestQueryNoParams(t *testing.T) {
|
||||
|
@ -280,14 +290,14 @@ func TestQueryInterfaceNoParam(t *testing.T) {
|
|||
records, err := testEngine.Table("get_var5").Limit(1).QueryInterface()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, 1, toInt64(records[0]["id"]))
|
||||
assert.EqualValues(t, 0, toInt64(records[0]["msg"]))
|
||||
assert.EqualValues(t, 1, records[0]["id"])
|
||||
assert.False(t, toBool(records[0]["msg"]))
|
||||
|
||||
records, err = testEngine.Table("get_var5").Where(builder.Eq{"id": 1}).QueryInterface()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, 1, toInt64(records[0]["id"]))
|
||||
assert.EqualValues(t, 0, toInt64(records[0]["msg"]))
|
||||
assert.EqualValues(t, 1, records[0]["id"])
|
||||
assert.False(t, toBool(records[0]["msg"]))
|
||||
}
|
||||
|
||||
func TestQueryWithBuilder(t *testing.T) {
|
||||
|
|
55
scan.go
55
scan.go
|
@ -7,10 +7,12 @@ package xorm
|
|||
import (
|
||||
"database/sql"
|
||||
|
||||
"xorm.io/xorm/convert"
|
||||
"xorm.io/xorm/core"
|
||||
"xorm.io/xorm/dialects"
|
||||
)
|
||||
|
||||
func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) {
|
||||
func row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) {
|
||||
var scanResults = make([]interface{}, len(fields))
|
||||
for i := 0; i < len(fields); i++ {
|
||||
var s sql.NullString
|
||||
|
@ -29,7 +31,7 @@ func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, field
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (engine *Engine) row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) {
|
||||
func row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) {
|
||||
var scanResults = make([]interface{}, len(fields))
|
||||
for i := 0; i < len(fields); i++ {
|
||||
var s sql.NullString
|
||||
|
@ -48,7 +50,7 @@ func (engine *Engine) row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fie
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) {
|
||||
func row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) {
|
||||
results := make([]string, 0, len(fields))
|
||||
var scanResults = make([]interface{}, len(fields))
|
||||
for i := 0; i < len(fields); i++ {
|
||||
|
@ -65,3 +67,50 @@ func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fie
|
|||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
|
||||
fields, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
types, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
result, err := row2mapBytes(rows, types, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultsSlice = append(resultsSlice, result)
|
||||
}
|
||||
|
||||
return resultsSlice, nil
|
||||
}
|
||||
|
||||
func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]interface{}, error) {
|
||||
var resultsMap = make(map[string]interface{}, len(fields))
|
||||
var scanResultContainers = make([]interface{}, len(fields))
|
||||
for i := 0; i < len(fields); i++ {
|
||||
scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scanResultContainers[i] = scanResult
|
||||
}
|
||||
if err := engine.driver.Scan(&dialects.ScanContext{
|
||||
DBLocation: engine.DatabaseTZ,
|
||||
UserLocation: engine.TZLocation,
|
||||
}, rows, types, scanResultContainers...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for ii, key := range fields {
|
||||
res, err := convert.Interface2Interface(engine.TZLocation, scanResultContainers[ii])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultsMap[key] = res
|
||||
}
|
||||
return resultsMap, nil
|
||||
}
|
||||
|
|
|
@ -5,13 +5,7 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm/core"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// Query runs a raw sql and return records as []map[string][]byte
|
||||
|
@ -28,53 +22,6 @@ func (session *Session) Query(sqlOrArgs ...interface{}) ([]map[string][]byte, er
|
|||
return session.queryBytes(sqlStr, args...)
|
||||
}
|
||||
|
||||
func value2String(rawValue *reflect.Value) (str string, err error) {
|
||||
aa := reflect.TypeOf((*rawValue).Interface())
|
||||
vv := reflect.ValueOf((*rawValue).Interface())
|
||||
switch aa.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
str = strconv.FormatInt(vv.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
str = strconv.FormatUint(vv.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
str = strconv.FormatFloat(vv.Float(), 'f', -1, 64)
|
||||
case reflect.String:
|
||||
str = vv.String()
|
||||
case reflect.Array, reflect.Slice:
|
||||
switch aa.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
data := rawValue.Interface().([]byte)
|
||||
str = string(data)
|
||||
if str == "\x00" {
|
||||
str = "0"
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
|
||||
}
|
||||
// time type
|
||||
case reflect.Struct:
|
||||
if aa.ConvertibleTo(schemas.TimeType) {
|
||||
str = vv.Convert(schemas.TimeType).Interface().(time.Time).Format(time.RFC3339Nano)
|
||||
} else {
|
||||
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
|
||||
}
|
||||
case reflect.Bool:
|
||||
str = strconv.FormatBool(vv.Bool())
|
||||
case reflect.Complex128, reflect.Complex64:
|
||||
str = fmt.Sprintf("%v", vv.Complex())
|
||||
/* TODO: unsupported types below
|
||||
case reflect.Map:
|
||||
case reflect.Ptr:
|
||||
case reflect.Uintptr:
|
||||
case reflect.UnsafePointer:
|
||||
case reflect.Chan, reflect.Func, reflect.Interface:
|
||||
*/
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) {
|
||||
fields, err := rows.Columns()
|
||||
if err != nil {
|
||||
|
@ -86,7 +33,7 @@ func (session *Session) rows2Strings(rows *core.Rows) (resultsSlice []map[string
|
|||
}
|
||||
|
||||
for rows.Next() {
|
||||
result, err := session.engine.row2mapStr(rows, types, fields)
|
||||
result, err := row2mapStr(rows, types, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -107,7 +54,7 @@ func (session *Session) rows2SliceString(rows *core.Rows) (resultsSlice [][]stri
|
|||
}
|
||||
|
||||
for rows.Next() {
|
||||
record, err := session.engine.row2sliceStr(rows, types, fields)
|
||||
record, err := row2sliceStr(rows, types, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -157,30 +104,17 @@ func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string,
|
|||
return session.rows2SliceString(rows)
|
||||
}
|
||||
|
||||
func row2mapInterface(rows *core.Rows, fields []string) (resultsMap map[string]interface{}, err error) {
|
||||
resultsMap = make(map[string]interface{}, len(fields))
|
||||
scanResultContainers := make([]interface{}, len(fields))
|
||||
for i := 0; i < len(fields); i++ {
|
||||
var scanResultContainer interface{}
|
||||
scanResultContainers[i] = &scanResultContainer
|
||||
}
|
||||
if err := rows.Scan(scanResultContainers...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for ii, key := range fields {
|
||||
resultsMap[key] = reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])).Interface()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) {
|
||||
func (session *Session) rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) {
|
||||
fields, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
types, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
result, err := row2mapInterface(rows, fields)
|
||||
result, err := session.engine.row2mapInterface(rows, types, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -207,5 +141,5 @@ func (session *Session) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]i
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
return rows2Interfaces(rows)
|
||||
return session.rows2Interfaces(rows)
|
||||
}
|
||||
|
|
|
@ -6,9 +6,13 @@ package xorm
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm/core"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) {
|
||||
|
@ -71,6 +75,53 @@ func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row {
|
|||
return core.NewRow(session.queryRows(sqlStr, args...))
|
||||
}
|
||||
|
||||
func value2String(rawValue *reflect.Value) (str string, err error) {
|
||||
aa := reflect.TypeOf((*rawValue).Interface())
|
||||
vv := reflect.ValueOf((*rawValue).Interface())
|
||||
switch aa.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
str = strconv.FormatInt(vv.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
str = strconv.FormatUint(vv.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
str = strconv.FormatFloat(vv.Float(), 'f', -1, 64)
|
||||
case reflect.String:
|
||||
str = vv.String()
|
||||
case reflect.Array, reflect.Slice:
|
||||
switch aa.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
data := rawValue.Interface().([]byte)
|
||||
str = string(data)
|
||||
if str == "\x00" {
|
||||
str = "0"
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
|
||||
}
|
||||
// time type
|
||||
case reflect.Struct:
|
||||
if aa.ConvertibleTo(schemas.TimeType) {
|
||||
str = vv.Convert(schemas.TimeType).Interface().(time.Time).Format(time.RFC3339Nano)
|
||||
} else {
|
||||
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
|
||||
}
|
||||
case reflect.Bool:
|
||||
str = strconv.FormatBool(vv.Bool())
|
||||
case reflect.Complex128, reflect.Complex64:
|
||||
str = fmt.Sprintf("%v", vv.Complex())
|
||||
/* TODO: unsupported types below
|
||||
case reflect.Map:
|
||||
case reflect.Ptr:
|
||||
case reflect.Uintptr:
|
||||
case reflect.UnsafePointer:
|
||||
case reflect.Chan, reflect.Func, reflect.Interface:
|
||||
*/
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func value2Bytes(rawValue *reflect.Value) ([]byte, error) {
|
||||
str, err := value2String(rawValue)
|
||||
if err != nil {
|
||||
|
@ -79,26 +130,6 @@ func value2Bytes(rawValue *reflect.Value) ([]byte, error) {
|
|||
return []byte(str), nil
|
||||
}
|
||||
|
||||
func (session *Session) rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
|
||||
fields, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
types, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
result, err := session.engine.row2mapBytes(rows, types, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultsSlice = append(resultsSlice, result)
|
||||
}
|
||||
|
||||
return resultsSlice, nil
|
||||
}
|
||||
|
||||
func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[string][]byte, error) {
|
||||
rows, err := session.queryRows(sqlStr, args...)
|
||||
if err != nil {
|
||||
|
@ -106,7 +137,7 @@ func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[st
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
return session.rows2maps(rows)
|
||||
return rows2maps(rows)
|
||||
}
|
||||
|
||||
func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user