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 "" } }