package sqimple import ( "database/sql" "errors" "fmt" "code.aneur.in/go/record" ) func ScanOneRecord(rows *sql.Rows) (record.Record, error) { records, err := ScanRecords(rows) if err != nil { return nil, err } if len(records) == 0 { return nil, errors.New("no rows") } return records[0], nil } // ScanRecords transforms SQL row data into record.Record objects, making them easier to handle with type safety. // // https://go.dev/ref/spec#Types func ScanRecords(rows *sql.Rows) ([]record.Record, error) { columns, err := rows.ColumnTypes() if err != nil { return nil, err } data := []record.Record{} for rows.Next() { values := []any{} for _, column := range columns { switch column.ScanType().Name() { case "bool": var value bool values = append(values, &value) case "byte": var value byte values = append(values, &value) case "complex64": var value complex64 values = append(values, &value) case "complex128": var value complex128 values = append(values, &value) case "float32": var value float32 values = append(values, &value) case "float64": var value float64 values = append(values, &value) case "int": var value int values = append(values, &value) case "int8": var value int8 values = append(values, &value) case "int16": var value int16 values = append(values, &value) case "int32": var value int32 values = append(values, &value) case "int64": var value int64 values = append(values, &value) case "rune": var value rune values = append(values, &value) case "string": var value string values = append(values, &value) case "uint": var value uint values = append(values, &value) case "uint8": var value uint8 values = append(values, &value) case "uint16": var value uint16 values = append(values, &value) case "uint32": var value uint32 values = append(values, &value) case "uint64": var value uint64 values = append(values, &value) default: return nil, fmt.Errorf("unknown scan type %q", column.ScanType().Name()) } } err := rows.Scan(values...) if err != nil { return nil, err } row := record.Record{} for i, column := range columns { switch column.ScanType().Name() { case "bool": value := values[i].(*bool) row[column.Name()] = *value case "byte": value := values[i].(*byte) row[column.Name()] = *value case "complex64": value := values[i].(*complex64) row[column.Name()] = *value case "complex128": value := values[i].(*complex128) row[column.Name()] = *value case "float32": value := values[i].(*float32) row[column.Name()] = *value case "float64": value := values[i].(*float64) row[column.Name()] = *value case "int": value := values[i].(*int) row[column.Name()] = *value case "int8": value := values[i].(*int8) row[column.Name()] = *value case "int16": value := values[i].(*int16) row[column.Name()] = *value case "int32": value := values[i].(*int32) row[column.Name()] = *value case "int64": value := values[i].(*int64) row[column.Name()] = *value case "rune": value := values[i].(*rune) row[column.Name()] = *value case "string": value := values[i].(*string) row[column.Name()] = *value case "uint": value := values[i].(*uint) row[column.Name()] = *value case "uint8": value := values[i].(*uint8) row[column.Name()] = *value case "uint16": value := values[i].(*uint16) row[column.Name()] = *value case "uint32": value := values[i].(*uint32) row[column.Name()] = *value case "uint64": value := values[i].(*uint64) row[column.Name()] = *value } } data = append(data, row) } return data, nil }