googleapis/mcp-toolbox · error
unable to get columns: %w
Error message
unable to get columns: %w
What it means
Wraps the error from rows.Columns() after a successful query in RunSQL. In database/sql this rarely fails, but the Firebird driver can fail here when the statement metadata is unavailable — typically for non-SELECT statements (DML without RETURNING) or a connection drop between query and metadata fetch.
Source
Thrown at internal/sources/firebird/firebird.go:113
func (s *Source) ToConfig() sources.SourceConfig {
return s.Config
}
func (s *Source) FirebirdDB() *sql.DB {
return s.Db
}
func (s *Source) RunSQL(ctx context.Context, statement string, params []any) (any, error) {
rows, err := s.FirebirdDB().QueryContext(ctx, statement, params...)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("unable to get columns: %w", err)
}
values := make([]any, len(cols))
scanArgs := make([]any, len(values))
for i := range values {
scanArgs[i] = &values[i]
}
out := []any{}
for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
return nil, fmt.Errorf("unable to parse row: %w", err)
}
vMap := make(map[string]any)
for i, col := range cols {View on GitHub (pinned to 8cc6e09de2)
Solutions
- Use statements that return a result set, or add RETURNING clause for DML needing columns
- Handle no-metadata statements separately (use ExecContext for DML/DDL)
- Check connection stability (SetConnMaxLifetime, server keepalive) if intermittent
Example fix
// before
cols, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("unable to get columns: %w", err)
}
// after
cols, err := rows.Columns()
if err != nil {
if errors.Is(err, sql.ErrNoRows) || errors.Is(err, driver.ErrSkip) {
return map[string]any{"status": "statement executed, no result set"}, nil
}
return nil, fmt.Errorf("unable to get columns: %w", err)
} Defensive patterns
Strategy: fallback
Validate before calling
// Prefer statements with result metadata; detect bare DML first
upper := strings.ToUpper(strings.TrimSpace(statement))
if strings.HasPrefix(upper, "INSERT") || strings.HasPrefix(upper, "UPDATE") ||
strings.HasPrefix(upper, "DELETE") || strings.HasPrefix(upper, "CREATE") {
// expect no result set; consider ExecContext path instead
} Type guard
func hasNoMetadata(err error) bool {
return err != nil && (errors.Is(err, sql.ErrNoRows) || strings.Contains(err.Error(), "no result set"))
} Try / catch
cols, err := rows.Columns()
if err != nil {
if hasNoMetadata(err) {
return map[string]any{"status": "executed", "rows": 0}, nil
}
return nil, fmt.Errorf("unable to get columns: %w", err)
} Prevention
- Route non-SELECT statements to an execute path, not the row-reading path
- Add RETURNING clauses when DML results are needed
- Keep the firebirdsql driver updated for metadata-handling fixes
- Avoid EXECUTE BLOCK shapes known to omit metadata in this driver
When it happens
Trigger: RunSQL called with an INSERT/UPDATE/DELETE without RETURNING clause (no result-set metadata), or the underlying connection died after rows were obtained.
Common situations: Using RunSQL for DDL/DML statements that return no result set; driver quirks with EXECUTE BLOCK statements; network interruption mid-call.
Related errors
- unable to get column types: %w
- unable to retrieve rows column name: %w
- unable to get column types: %w
- unable to retrieve rows column name: %w
- unable to retrieve rows column name: %w
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/e4d2a8e341a9c8ac.
Report an issue: GitHub.