169 lines
3.4 KiB
Go
169 lines
3.4 KiB
Go
package stardb
|
|
|
|
import (
|
|
"database/sql"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// StarRows represents a result set from a query
|
|
type StarRows struct {
|
|
rows *sql.Rows
|
|
db *StarDB
|
|
length int
|
|
stringResult []map[string]string
|
|
columns []string
|
|
columnsType []reflect.Type
|
|
columnIndex map[string]int
|
|
data [][]interface{}
|
|
parsed bool
|
|
}
|
|
|
|
// Length returns the number of rows
|
|
func (r *StarRows) Length() int {
|
|
return r.length
|
|
}
|
|
|
|
// StringResult returns all rows as string maps
|
|
func (r *StarRows) StringResult() []map[string]string {
|
|
return r.stringResult
|
|
}
|
|
|
|
// Columns returns column names
|
|
func (r *StarRows) Columns() []string {
|
|
return r.columns
|
|
}
|
|
|
|
// ColumnsType returns column types
|
|
func (r *StarRows) ColumnsType() []reflect.Type {
|
|
return r.columnsType
|
|
}
|
|
|
|
// Close closes the result set
|
|
func (r *StarRows) Close() error {
|
|
return r.rows.Close()
|
|
}
|
|
|
|
// Rescan re-parses the result set
|
|
func (r *StarRows) Rescan() error {
|
|
return r.parse()
|
|
}
|
|
|
|
// Row returns a specific row by index
|
|
func (r *StarRows) Row(index int) *StarResult {
|
|
result := &StarResult{}
|
|
if index >= len(r.data) {
|
|
return result
|
|
}
|
|
result.result = r.data[index]
|
|
result.columns = r.columns
|
|
result.columnsType = r.columnsType
|
|
result.columnIndex = r.columnIndex
|
|
return result
|
|
}
|
|
|
|
// Col returns all values for a specific column
|
|
func (r *StarRows) Col(name string) *StarResultCol {
|
|
result := &StarResultCol{}
|
|
if _, ok := r.columnIndex[name]; !ok {
|
|
return result
|
|
}
|
|
|
|
colIndex := r.columnIndex[name]
|
|
for _, row := range r.data {
|
|
result.result = append(result.result, row[colIndex])
|
|
}
|
|
return result
|
|
}
|
|
|
|
// parse parses the sql.Rows into internal data structures
|
|
func (r *StarRows) parse() error {
|
|
if r.parsed {
|
|
return nil
|
|
}
|
|
defer func() {
|
|
r.parsed = true
|
|
}()
|
|
|
|
r.data = [][]interface{}{}
|
|
r.columnIndex = make(map[string]int)
|
|
r.stringResult = []map[string]string{}
|
|
|
|
var err error
|
|
r.columns, err = r.rows.Columns()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
columnTypes, err := r.rows.ColumnTypes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, colType := range columnTypes {
|
|
r.columnsType = append(r.columnsType, colType.ScanType())
|
|
}
|
|
|
|
// Build column index map
|
|
for i, colName := range r.columns {
|
|
r.columnIndex[colName] = i
|
|
}
|
|
|
|
// Prepare scan arguments
|
|
scanArgs := make([]interface{}, len(r.columns))
|
|
values := make([]interface{}, len(r.columns))
|
|
for i := range values {
|
|
scanArgs[i] = &values[i]
|
|
}
|
|
|
|
// Scan all rows
|
|
for r.rows.Next() {
|
|
if err := r.rows.Scan(scanArgs...); err != nil {
|
|
return err
|
|
}
|
|
|
|
record := make(map[string]string)
|
|
rowCopy := make([]interface{}, len(values))
|
|
|
|
for i, val := range values {
|
|
rowCopy[i] = val
|
|
record[r.columns[i]] = convertToString(val)
|
|
}
|
|
|
|
r.data = append(r.data, rowCopy)
|
|
r.stringResult = append(r.stringResult, record)
|
|
}
|
|
|
|
r.length = len(r.stringResult)
|
|
return nil
|
|
}
|
|
|
|
// convertToString converts any value to string
|
|
func convertToString(val interface{}) string {
|
|
switch v := val.(type) {
|
|
case nil:
|
|
return ""
|
|
case string:
|
|
return v
|
|
case int:
|
|
return strconv.Itoa(v)
|
|
case int32:
|
|
return strconv.FormatInt(int64(v), 10)
|
|
case int64:
|
|
return strconv.FormatInt(v, 10)
|
|
case float32:
|
|
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
|
case float64:
|
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
|
case bool:
|
|
return strconv.FormatBool(v)
|
|
case time.Time:
|
|
return v.String()
|
|
case []byte:
|
|
return string(v)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|