googleapis/mcp-toolbox · error

unable to get column types: %w

Error message

unable to get column types: %w

What it means

This error wraps a failure from results.ColumnTypes() in RunSQL. ColumnTypes() fetches detailed type information for each column; an error here indicates the driver lost access to the result metadata, typically because the connection or result set became invalid right after Columns() succeeded.

Source

Thrown at internal/sources/cloudsqlmysql/cloud_sql_mysql.go:155

		return nil, fmt.Errorf("unable to execute query: %w", err)
	}
	defer results.Close()

	cols, err := results.Columns()
	if err != nil {
		return nil, fmt.Errorf("unable to retrieve rows column name: %w", err)
	}

	// create an array of values for each column, which can be re-used to scan each row
	rawValues := make([]any, len(cols))
	values := make([]any, len(cols))
	for i := range rawValues {
		values[i] = &rawValues[i]
	}

	colTypes, err := results.ColumnTypes()
	if err != nil {
		return nil, fmt.Errorf("unable to get column types: %w", err)
	}

	out := []any{}
	for results.Next() {
		err := results.Scan(values...)
		if err != nil {
			return nil, fmt.Errorf("unable to parse row: %w", err)
		}
		row := orderedmap.Row{}
		for i, name := range cols {
			val := rawValues[i]
			if val == nil {
				row.Add(name, nil)
				continue
			}

			convertedValue, err := mysqlcommon.ConvertToType(colTypes[i], val)
			if err != nil {

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Retry the query — this is nearly always transient between two adjacent driver calls.
  2. Keep pooled connections healthy (SetConnMaxLifetime, ping-before-use) so the connection is not reaped mid-request.
  3. Check Cloud SQL instance health/logs for restarts or failovers coinciding with the error.
  4. Narrow result sets (fewer/wider types) if driver type parsing is implicated; upgrade the mysql driver if a known bug matches.
  5. Verify network path stability as with other mid-query connection drops.

Example fix

// before
pool.SetMaxOpenConns(10)
// after
pool.SetMaxOpenConns(10)
pool.SetConnMaxLifetime(5 * time.Minute)
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
Defensive patterns

Strategy: retry

Validate before calling

func ensureHealthyPool(db *sql.DB) error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := db.PingContext(ctx); err != nil {
        return fmt.Errorf("pool unhealthy before query: %w", err)
    }
    return nil
}

Type guard

func isTransientConnectionLoss(err error) bool {
    msg := err.Error()
    return strings.Contains(msg, "broken pipe") ||
        strings.Contains(msg, "bad connection") ||
        strings.Contains(msg, "connection reset") ||
        errors.Is(err, io.EOF) ||
        errors.Is(err, context.Canceled)
}

Try / catch

var result any
var err error
for attempt := 0; attempt < 2; attempt++ {
    result, err = src.RunSQL(ctx, statement, params)
    if err == nil {
        break
    }
    if !strings.Contains(err.Error(), "column types") || !isTransientConnectionLoss(err) {
        break
    }
    time.Sleep(200 * time.Millisecond)
}

Prevention

When it happens

Trigger: Calling RunSQL when the MySQL connection is interrupted between the Columns() call and ColumnTypes(), or the driver fails to parse/return column type descriptors for the result set.

Common situations: Server failover or connection kill mid-metadata-fetch, transient network errors, driver bugs with exotic column types or collations, extremely wide result sets over flaky links.

Related errors


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