I'd like to access column types in sql.Rows, but I can only get column names and prefer not to descend into "unsafe" teritory.
In my case it's for a codegenerator, but it would also be very useful for user-specified queries, generated queries or queries on unknown tables.
The type information should already be known to the driver, I just can't get it out.
What's missing is a way to unwrap it from the sql structs (Row and Rows) to get the full data provided by the driver (Rows.rowsi).
If a driver can access its own data given a *sql.Rows, it can provide column type information.
I imagine something like this would work:
database/sql/driver - new exported interface:
type Inspector interface {
SetInspector(inspect func(sqlStruct interface{}) (interface{}, error))
}
database/sql - new func:
func inspect(sqlStruct interface{}) (interface{}, error) {
switch unwrapped := sqlStruct.(type) {
case *Row:
return unwrapped.rows.rowsi, nil
case *Rows:
return unwrapped.rowsi, nil
}
return nil, errors.New("sqlStruct could not be inspected")
}
database/sql - in Register after error checking:
if inspector, ok := driver.(driver.Inspector); ok {
driver.SetInspector(inspect)
}
If required, cases for *Tx, *DB and *Stmt could be added to inspect.
The proposal gives drivers a way to access their own data in the wrapped structs and exports only one interface in the driver package.
The driver only has to store the inspect function it receives on registration.
Only the driver can access this information, it's free to expose it in a safe way.
The sql package keeps responsibility for unwrapping should the internal structs change.
Regarding the driver, it only has to store the function it receives after Registration.
func Fields(rows interface{}) ([]Field, error) {
nativeRows, err := inspect(rows)
if err != nil {
return nil, err
}
myRows, ok := nativeRows.(*mysqlRows)
if !ok {
return nil, errors.New("rows must be a mysql rows object")
}
fields := make([]Field, len(myRows.columns))
for i, f := range myRows.columns {
fields[i] = &f
}
return fields, nil
}
type Field interface {
Name() string
MysqlType() string
MysqlDeclaration(params ...interface{}) (string, error)
ReflectType() (reflect.Type, error)
IsNumber() bool
IsInteger() bool
IsFloatingPoint() bool
IsDecimal() bool
IsText() bool
IsBlob() bool
IsTime() bool
IsPrimaryKey() bool
IsUniqueKey() bool
IsMultipleKey() bool
IsNotNull() bool
IsUnsigned() bool
IsZerofill() bool
IsBinary() bool
IsAutoIncrement() bool
}
And a user would call fields, err := mysql.Fields(rows) after a query.
What do you think?