stardb/rows.go

169 lines
3.4 KiB
Go
Raw Normal View History

2026-03-07 19:27:44 +08:00
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 ""
}
}