Accessing SQLite Column Metadata
@josharian wrote on Gophers Slack
====
I find myself wanting sqlite3_column_origin_name and I wanted to talk about API shape for that.
One idea is to do something kinda like Limit:
```go
type ColumnInfo struct {
Name string // sqlite3_column_name
DeclType string // sqlite3_column_decltype
DatabaseName string // sqlite3_column_database_name
TableName string // sqlite3_column_table_name
OriginName string // sqlite3_column_origin_name
}
func Columns(c *sql.Conn, query string) ([]ColumnInfo, error)
```
This lets you get info about the columns for a query.
But that's a one-shot deal. Another is to make a bigger API change that lays the groundwork for more such additions later (other things that take a sqlite3_stmt*), maybe roughly like:
```go
type Stmt struct { ... }
func Prepare(c *sql.Conn, query string) (*Stmt, error)
func (s *Stmt) Close() error
func (s *Stmt) Columns() []ColumnInfo
func (s *Stmt) ReadOnly() bool
func (s *Stmt) NormalizedSQL() string
// ...
```
I'm also very open to other ideas. Happy to do the legwork here if/when there's an API everyone is happy with.
====
Aswering here because it is a bit long-ish.
Thanks for bringing this up and offering to do the legwork. I hope I understand the need for sqlite3\_column\_origin\_name and related metadata, especially if you are building ORMs, data mappers, or dynamic query builders.
The good news is that I checked the generated code, and the underlying C functions (Xsqlite3\_column\_origin\_name, etc.) are actually already exposed in our translation. This means we don't have to worry about custom builds or C-preprocessor macros—the technical foundation is already there\!
However, I am a bit hesitant to go with the \*Stmt API approach. The main issue is Go's strict encapsulation within the database/sql standard library.
When a user calls db.Prepare(), Go wraps our driver.Stmt inside its own opaque \*sql.Stmt. Go intentionally provides no way to unwrap that \*sql.Stmt to get the underlying driver statement back. Because of this, if we built a custom \*Stmt API, users would have to completely abandon the standard database/sql workflow to use it. I want to avoid building a parallel API to the standard library to prevent ecosystem fragmentation.
**The Proposed Compromise**
The idiomatic Go way to expose driver-specific features without breaking abstractions is through *sql.Conn.Raw() (introduced in Go 1.13).
Because Raw() gives users access to the underlying driver connection (\*conn), we can attach a metadata method directly to that connection object. Since the user can't pass an unwrapped statement handle, this method will take the raw query string, briefly prepare it, extract the metadata, and finalize it. (While preparing twice might seem slightly inefficient, SQLite local prepares are extremely fast, and this metadata is usually cached heavily by ORMs anyway).
If we add something like this to our internal *conn type:
```go
type ColumnInfo struct {
Name string
DeclType string
DatabaseName string
TableName string
OriginName string
}
// Attached to the internal driver connection
func (c *conn) SQLiteColumnInfo(query string) ([]ColumnInfo, error) {
// ... internally prepare, read metadata using Xsqlite3_column_origin_name, close ...
}
```
Users could access it using standard standard library methods like this:
```go
conn, err := db.Conn(ctx)
// ... error handling ...
err = conn.Raw(func(driverConn any) error {
// Define an interface to type-assert against
type columnInformer interface {
SQLiteColumnInfo(query string) ([]sqlite.ColumnInfo, error)
}
if ci, ok := driverConn.(columnInformer); ok {
info, err := ci.SQLiteColumnInfo("SELECT id AS user_id FROM users")
// You now have access to OriginName, TableName, etc!
}
return nil
})
```
This gives you exactly the rich metadata you need, but it keeps the public API surface clean and forces everything through standard database/sql channels.
If you are happy with this API shape, I would absolutely welcome a PR implementing SQLiteColumnInfo on the internal connection\! Let me know what you think.
WDYT?
task