diff --git a/engine.go b/engine.go index eababd40..0d34bf96 100644 --- a/engine.go +++ b/engine.go @@ -1354,6 +1354,13 @@ func (engine *Engine) QueryString(sqlStr string, args ...interface{}) ([]map[str return session.QueryString(sqlStr, args...) } +// QueryInterface runs a raw sql and return records as []map[string]interface{} +func (engine *Engine) QueryInterface(sqlStr string, args ...interface{}) ([]map[string]interface{}, error) { + session := engine.NewSession() + defer session.Close() + return session.QueryInterface(sqlStr, args...) +} + // Insert one or more records func (engine *Engine) Insert(beans ...interface{}) (int64, error) { session := engine.NewSession() diff --git a/session_query.go b/session_query.go new file mode 100644 index 00000000..989efdf8 --- /dev/null +++ b/session_query.go @@ -0,0 +1,182 @@ +// Copyright 2017 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 xorm + +import ( + "fmt" + "reflect" + "strconv" + "time" + + "github.com/go-xorm/core" +) + +// Query runs a raw sql and return records as []map[string][]byte +func (session *Session) Query(sqlStr string, args ...interface{}) ([]map[string][]byte, error) { + if session.isAutoClose { + defer session.Close() + } + + return session.queryBytes(sqlStr, args...) +} + +func reflect2value(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) + default: + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + // time type + case reflect.Struct: + if aa.ConvertibleTo(core.TimeType) { + str = vv.Convert(core.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 value2String(rawValue *reflect.Value) (data string, err error) { + data, err = reflect2value(rawValue) + if err != nil { + return + } + return +} + +func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { + result := make(map[string]string) + 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 { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + // if row is null then as empty string + if rawValue.Interface() == nil { + result[key] = "" + continue + } + + if data, err := value2String(&rawValue); err == nil { + result[key] = data + } else { + return nil, err + } + } + return result, nil +} + +func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2mapStr(rows, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + + return resultsSlice, nil +} + +// QueryString runs a raw sql and return records as []map[string]string +func (session *Session) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) { + if session.isAutoClose { + defer session.Close() + } + + rows, err := session.queryRows(sqlStr, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + return rows2Strings(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) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2mapInterface(rows, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + + return resultsSlice, nil +} + +// QueryInterface runs a raw sql and return records as []map[string]interface{} +func (session *Session) QueryInterface(sqlStr string, args ...interface{}) ([]map[string]interface{}, error) { + if session.isAutoClose { + defer session.Close() + } + + rows, err := session.queryRows(sqlStr, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + return rows2Interfaces(rows) +} diff --git a/session_query_test.go b/session_query_test.go new file mode 100644 index 00000000..61a0a7fc --- /dev/null +++ b/session_query_test.go @@ -0,0 +1,112 @@ +// Copyright 2017 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 xorm + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestQueryString(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type GetVar struct { + Id int64 `xorm:"autoincr pk"` + Msg string `xorm:"varchar(255)"` + Age int + Money float32 + Created time.Time `xorm:"created"` + } + + assert.NoError(t, testEngine.Sync2(new(GetVar))) + + var data = GetVar{ + Msg: "hi", + Age: 28, + Money: 1.5, + } + _, err := testEngine.InsertOne(data) + assert.NoError(t, err) + + records, err := testEngine.QueryString("select * from get_var") + assert.NoError(t, err) + assert.Equal(t, 1, len(records)) + assert.Equal(t, 5, len(records[0])) + assert.Equal(t, "1", records[0]["id"]) + assert.Equal(t, "hi", records[0]["msg"]) + assert.Equal(t, "28", records[0]["age"]) + assert.Equal(t, "1.5", records[0]["money"]) +} + +func toString(i interface{}) string { + switch i.(type) { + case []byte: + return string(i.([]byte)) + case string: + return i.(string) + } + return fmt.Sprintf("%v", i) +} + +func toInt64(i interface{}) int64 { + switch i.(type) { + case []byte: + n, _ := strconv.ParseInt(string(i.([]byte)), 10, 64) + return n + case int: + return int64(i.(int)) + case int64: + return i.(int64) + } + return 0 +} + +func toFloat64(i interface{}) float64 { + switch i.(type) { + case []byte: + n, _ := strconv.ParseFloat(string(i.([]byte)), 64) + return n + case float64: + return i.(float64) + case float32: + return float64(i.(float32)) + } + return 0 +} + +func TestQueryInterface(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type GetVarInterface struct { + Id int64 `xorm:"autoincr pk"` + Msg string `xorm:"varchar(255)"` + Age int + Money float32 + Created time.Time `xorm:"created"` + } + + assert.NoError(t, testEngine.Sync2(new(GetVarInterface))) + + var data = GetVarInterface{ + Msg: "hi", + Age: 28, + Money: 1.5, + } + _, err := testEngine.InsertOne(data) + assert.NoError(t, err) + + records, err := testEngine.QueryInterface("select * from get_var_interface") + 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"])) +} diff --git a/session_raw.go b/session_raw.go index 67f042fd..46d0eba0 100644 --- a/session_raw.go +++ b/session_raw.go @@ -6,164 +6,12 @@ package xorm import ( "database/sql" - "fmt" "reflect" - "strconv" "time" "github.com/go-xorm/core" ) -func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := row2map(rows, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - - return resultsSlice, nil -} - -func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { - var str string - str, err = reflect2value(rawValue) - if err != nil { - return - } - data = []byte(str) - return -} - -func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { - result := make(map[string][]byte) - 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 { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - //if row is null then ignore - if rawValue.Interface() == nil { - //fmt.Println("ignore ...", key, rawValue) - continue - } - - if data, err := value2Bytes(&rawValue); err == nil { - result[key] = data - } else { - return nil, err // !nashtsai! REVIEW, should return err or just error log? - } - } - return result, nil -} - -func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := row2mapStr(rows, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - - return resultsSlice, nil -} - -func reflect2value(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) - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - // time type - case reflect.Struct: - if aa.ConvertibleTo(core.TimeType) { - str = vv.Convert(core.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 value2String(rawValue *reflect.Value) (data string, err error) { - data, err = reflect2value(rawValue) - if err != nil { - return - } - return -} - -func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { - result := make(map[string]string) - 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 { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - // if row is null then as empty string - if rawValue.Interface() == nil { - result[key] = "" - continue - } - - if data, err := value2String(&rawValue); err == nil { - result[key] = data - } else { - return nil, err - } - } - return result, nil -} - func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { for _, filter := range session.engine.dialect.Filters() { *sqlStr = filter.Do(*sqlStr, session.engine.dialect, session.statement.RefTable) @@ -231,6 +79,60 @@ func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row { return core.NewRow(session.queryRows(sqlStr, args...)) } +func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { + var str string + str, err = reflect2value(rawValue) + if err != nil { + return + } + data = []byte(str) + return +} + +func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { + result := make(map[string][]byte) + 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 { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + //if row is null then ignore + if rawValue.Interface() == nil { + result[key] = []byte{} + continue + } + + if data, err := value2Bytes(&rawValue); err == nil { + result[key] = data + } else { + return nil, err // !nashtsai! REVIEW, should return err or just error log? + } + } + return result, nil +} + +func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2map(rows, 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 { @@ -241,30 +143,6 @@ func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[st return rows2maps(rows) } -// Query runs a raw sql and return records as []map[string][]byte -func (session *Session) Query(sqlStr string, args ...interface{}) ([]map[string][]byte, error) { - if session.isAutoClose { - defer session.Close() - } - - return session.queryBytes(sqlStr, args...) -} - -// QueryString runs a raw sql and return records as []map[string]string -func (session *Session) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) { - if session.isAutoClose { - defer session.Close() - } - - rows, err := session.queryRows(sqlStr, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - return rows2Strings(rows) -} - func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { defer session.resetStatement() diff --git a/session_raw_test.go b/session_raw_test.go index 1f2145b2..cf381974 100644 --- a/session_raw_test.go +++ b/session_raw_test.go @@ -7,42 +7,10 @@ package xorm import ( "strconv" "testing" - "time" "github.com/stretchr/testify/assert" ) -func TestQueryString(t *testing.T) { - assert.NoError(t, prepareEngine()) - - type GetVar struct { - Id int64 `xorm:"autoincr pk"` - Msg string `xorm:"varchar(255)"` - Age int - Money float32 - Created time.Time `xorm:"created"` - } - - assert.NoError(t, testEngine.Sync2(new(GetVar))) - - var data = GetVar{ - Msg: "hi", - Age: 28, - Money: 1.5, - } - _, err := testEngine.InsertOne(data) - assert.NoError(t, err) - - records, err := testEngine.QueryString("select * from get_var") - assert.NoError(t, err) - assert.Equal(t, 1, len(records)) - assert.Equal(t, 5, len(records[0])) - assert.Equal(t, "1", records[0]["id"]) - assert.Equal(t, "hi", records[0]["msg"]) - assert.Equal(t, "28", records[0]["age"]) - assert.Equal(t, "1.5", records[0]["money"]) -} - func TestQuery(t *testing.T) { assert.NoError(t, prepareEngine())