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

  1. Use statements that return a result set, or add RETURNING clause for DML needing columns
  2. Handle no-metadata statements separately (use ExecContext for DML/DDL)
  3. 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

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


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/e4d2a8e341a9c8ac. Report an issue: GitHub.