Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,20 +224,16 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
columnCount, err := stmt.readPrepareResultPacket()
if err == nil {
if stmt.paramCount > 0 {
if err = mc.skipColumns(stmt.paramCount); err != nil {
// Read parameter metadata instead of skipping it
if stmt.params, err = mc.readColumns(stmt.paramCount, nil); err != nil {
return nil, err
}
}

if columnCount > 0 {
if mc.extCapabilities&clientCacheMetadata != 0 {
if stmt.columns, err = mc.readColumns(int(columnCount), nil); err != nil {
return nil, err
}
} else {
if err = mc.skipColumns(int(columnCount)); err != nil {
return nil, err
}
// Always read column metadata
if stmt.columns, err = mc.readColumns(int(columnCount), stmt.columns); err != nil {
return nil, err
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,38 @@ type mysqlField struct {
charSet uint8
}

// FieldMetadata represents metadata about a column or parameter from a prepared statement.
// This is a public struct that exposes the metadata from mysqlField.
type FieldMetadata struct {
// TableName is the name of the table this field belongs to (may be empty for expressions)
TableName string
// Name is the name or alias of the field
Name string
// Length is the maximum length of the field
Length uint32
// Decimals is the number of decimals for numeric types
Decimals byte
// DatabaseTypeName returns the MySQL type name (e.g., "INT", "VARCHAR", "TEXT")
DatabaseTypeName string
// Nullable indicates whether the field can be NULL
Nullable bool
// Unsigned indicates whether a numeric field is unsigned
Unsigned bool
}

// toFieldMetadata converts an internal mysqlField to a public FieldMetadata
func (mf *mysqlField) toFieldMetadata() FieldMetadata {
return FieldMetadata{
TableName: mf.tableName,
Name: mf.name,
Length: mf.length,
Decimals: mf.decimals,
DatabaseTypeName: mf.typeDatabaseName(),
Nullable: mf.flags&flagNotNULL == 0,
Unsigned: mf.flags&flagUnsigned != 0,
}
}

func (mf *mysqlField) scanType() reflect.Type {
switch mf.fieldType {
case fieldTypeTiny:
Expand Down
48 changes: 48 additions & 0 deletions statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,38 @@ import (
"reflect"
)

// StmtMetadata is an interface that provides access to prepared statement metadata.
// It can be used via type assertion on the driver.Stmt returned by Conn.Prepare.
//
// Example usage with database/sql:
//
// conn, _ := db.Conn(ctx)
// conn.Raw(func(driverConn any) error {
// if preparer, ok := driverConn.(driver.ConnPrepareContext); ok {
// stmt, _ := preparer.PrepareContext(ctx, query)
// if meta, ok := stmt.(mysql.StmtMetadata); ok {
// columns := meta.ColumnMetadata()
// params := meta.ParamMetadata()
// }
// }
// return nil
// })
type StmtMetadata interface {
// ColumnMetadata returns metadata about result columns
ColumnMetadata() []FieldMetadata
// ParamMetadata returns metadata about query parameters
ParamMetadata() []FieldMetadata
}

// Verify that mysqlStmt implements StmtMetadata
var _ StmtMetadata = (*mysqlStmt)(nil)

type mysqlStmt struct {
mc *mysqlConn
id uint32
paramCount int
columns []mysqlField
params []mysqlField
}

func (stmt *mysqlStmt) Close() error {
Expand All @@ -42,6 +69,27 @@ func (stmt *mysqlStmt) NumInput() int {
return stmt.paramCount
}

// ColumnMetadata returns metadata about the columns that will be returned by this prepared statement.
// This information is obtained from the MySQL server during the PREPARE phase.
func (stmt *mysqlStmt) ColumnMetadata() []FieldMetadata {
result := make([]FieldMetadata, len(stmt.columns))
for i, col := range stmt.columns {
result[i] = col.toFieldMetadata()
}
return result
}

// ParamMetadata returns metadata about the parameters expected by this prepared statement.
// This information is obtained from the MySQL server during the PREPARE phase.
// Note: MySQL may return limited parameter metadata depending on the query structure.
func (stmt *mysqlStmt) ParamMetadata() []FieldMetadata {
result := make([]FieldMetadata, len(stmt.params))
for i, param := range stmt.params {
result[i] = param.toFieldMetadata()
}
return result
}

func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
return converter{}
}
Expand Down