Skip to content

Commit d81e1ca

Browse files
kyleconroyclaude
andcommitted
feat: Expose prepared statement column and parameter metadata
This adds public APIs to access metadata from COM_STMT_PREPARE responses: - Add FieldMetadata struct to expose column/parameter information - Add StmtMetadata interface with ColumnMetadata() and ParamMetadata() methods - Modify Prepare() to read parameter metadata instead of skipping it - Modify Prepare() to always read column metadata (not just with cache capability) The FieldMetadata struct exposes: - TableName, Name, Length, Decimals - DatabaseTypeName (e.g., "INT", "VARCHAR", "TEXT") - Nullable and Unsigned flags This allows tools like sqlc to get type information for query parameters and result columns directly from prepared statements. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 76c00e3 commit d81e1ca

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

connection.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,20 +224,16 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
224224
columnCount, err := stmt.readPrepareResultPacket()
225225
if err == nil {
226226
if stmt.paramCount > 0 {
227-
if err = mc.skipColumns(stmt.paramCount); err != nil {
227+
// Read parameter metadata instead of skipping it
228+
if stmt.params, err = mc.readColumns(stmt.paramCount, nil); err != nil {
228229
return nil, err
229230
}
230231
}
231232

232233
if columnCount > 0 {
233-
if mc.extCapabilities&clientCacheMetadata != 0 {
234-
if stmt.columns, err = mc.readColumns(int(columnCount), nil); err != nil {
235-
return nil, err
236-
}
237-
} else {
238-
if err = mc.skipColumns(int(columnCount)); err != nil {
239-
return nil, err
240-
}
234+
// Always read column metadata
235+
if stmt.columns, err = mc.readColumns(int(columnCount), stmt.columns); err != nil {
236+
return nil, err
241237
}
242238
}
243239
}

fields.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,38 @@ type mysqlField struct {
150150
charSet uint8
151151
}
152152

153+
// FieldMetadata represents metadata about a column or parameter from a prepared statement.
154+
// This is a public struct that exposes the metadata from mysqlField.
155+
type FieldMetadata struct {
156+
// TableName is the name of the table this field belongs to (may be empty for expressions)
157+
TableName string
158+
// Name is the name or alias of the field
159+
Name string
160+
// Length is the maximum length of the field
161+
Length uint32
162+
// Decimals is the number of decimals for numeric types
163+
Decimals byte
164+
// DatabaseTypeName returns the MySQL type name (e.g., "INT", "VARCHAR", "TEXT")
165+
DatabaseTypeName string
166+
// Nullable indicates whether the field can be NULL
167+
Nullable bool
168+
// Unsigned indicates whether a numeric field is unsigned
169+
Unsigned bool
170+
}
171+
172+
// toFieldMetadata converts an internal mysqlField to a public FieldMetadata
173+
func (mf *mysqlField) toFieldMetadata() FieldMetadata {
174+
return FieldMetadata{
175+
TableName: mf.tableName,
176+
Name: mf.name,
177+
Length: mf.length,
178+
Decimals: mf.decimals,
179+
DatabaseTypeName: mf.typeDatabaseName(),
180+
Nullable: mf.flags&flagNotNULL == 0,
181+
Unsigned: mf.flags&flagUnsigned != 0,
182+
}
183+
}
184+
153185
func (mf *mysqlField) scanType() reflect.Type {
154186
switch mf.fieldType {
155187
case fieldTypeTiny:

statement.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,38 @@ import (
1616
"reflect"
1717
)
1818

19+
// StmtMetadata is an interface that provides access to prepared statement metadata.
20+
// It can be used via type assertion on the driver.Stmt returned by Conn.Prepare.
21+
//
22+
// Example usage with database/sql:
23+
//
24+
// conn, _ := db.Conn(ctx)
25+
// conn.Raw(func(driverConn any) error {
26+
// if preparer, ok := driverConn.(driver.ConnPrepareContext); ok {
27+
// stmt, _ := preparer.PrepareContext(ctx, query)
28+
// if meta, ok := stmt.(mysql.StmtMetadata); ok {
29+
// columns := meta.ColumnMetadata()
30+
// params := meta.ParamMetadata()
31+
// }
32+
// }
33+
// return nil
34+
// })
35+
type StmtMetadata interface {
36+
// ColumnMetadata returns metadata about result columns
37+
ColumnMetadata() []FieldMetadata
38+
// ParamMetadata returns metadata about query parameters
39+
ParamMetadata() []FieldMetadata
40+
}
41+
42+
// Verify that mysqlStmt implements StmtMetadata
43+
var _ StmtMetadata = (*mysqlStmt)(nil)
44+
1945
type mysqlStmt struct {
2046
mc *mysqlConn
2147
id uint32
2248
paramCount int
2349
columns []mysqlField
50+
params []mysqlField
2451
}
2552

2653
func (stmt *mysqlStmt) Close() error {
@@ -42,6 +69,27 @@ func (stmt *mysqlStmt) NumInput() int {
4269
return stmt.paramCount
4370
}
4471

72+
// ColumnMetadata returns metadata about the columns that will be returned by this prepared statement.
73+
// This information is obtained from the MySQL server during the PREPARE phase.
74+
func (stmt *mysqlStmt) ColumnMetadata() []FieldMetadata {
75+
result := make([]FieldMetadata, len(stmt.columns))
76+
for i, col := range stmt.columns {
77+
result[i] = col.toFieldMetadata()
78+
}
79+
return result
80+
}
81+
82+
// ParamMetadata returns metadata about the parameters expected by this prepared statement.
83+
// This information is obtained from the MySQL server during the PREPARE phase.
84+
// Note: MySQL may return limited parameter metadata depending on the query structure.
85+
func (stmt *mysqlStmt) ParamMetadata() []FieldMetadata {
86+
result := make([]FieldMetadata, len(stmt.params))
87+
for i, param := range stmt.params {
88+
result[i] = param.toFieldMetadata()
89+
}
90+
return result
91+
}
92+
4593
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
4694
return converter{}
4795
}

0 commit comments

Comments
 (0)