googleapis/mcp-toolbox · error

error iterating rows: %w

Error message

error iterating rows: %w

What it means

Wraps rows.Err() after row iteration in RunSQL — the deferred error that database/sql accumulates if the result stream broke mid-iteration (network drop, server killed the connection, context cancelled). This is distinct from scan failures; it means rows were being fetched fine and then the stream failed.

Source

Thrown at internal/sources/firebird/firebird.go:142

		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 {
			if b, ok := values[i].([]byte); ok {
				vMap[col] = string(b)
			} else {
				vMap[col] = values[i]
			}
		}
		out = append(out, vMap)
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating rows: %w", err)
	}

	// In most cases, DML/DDL statements like INSERT, UPDATE, CREATE, etc. might return no rows
	// However, it is also possible that this was a query that was expected to return rows
	// but returned none, a case that we cannot distinguish here.
	return out, nil
}

func initFirebirdConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string) (*sql.DB, error) {
	_, span := sources.InitConnectionSpan(ctx, tracer, SourceType, name)
	defer span.End()

	// urlExample := "user:password@host:port/path/to/database.fdb"
	dsn := fmt.Sprintf("%s:%s@%s:%s/%s", user, pass, host, port, dbname)

	db, err := sql.Open("firebirdsql", dsn)
	if err != nil {
		return nil, fmt.Errorf("unable to create connection pool: %w", err)

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Paginate large results with FIRST/SKIP or a keyset (WHERE id > ?) instead of one huge result set
  2. Ensure ctx timeout exceeds expected fetch duration, or use context.WithoutCancel for fetch-only work
  3. Enable TCP keepalives / adjust firewall idle timeouts
  4. Retry transient failures at the caller for read-only queries

Example fix

// before
if err := rows.Err(); err != nil {
    return nil, fmt.Errorf("error iterating rows: %w", err)
}
// after
if err := rows.Err(); err != nil {
    if ctx.Err() != nil {
        return nil, fmt.Errorf("error iterating rows: %w", ctx.Err())
    }
    return nil, fmt.Errorf("error iterating rows: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// Bound result size before fetching
if !strings.Contains(strings.ToUpper(statement), "FIRST") {
    return errors.New("unbounded query: add FIRST/SKIP pagination for large result sets")
}

Type guard

func isTransientStreamErr(err error, ctx context.Context) bool {
    return ctx.Err() == nil && (errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET))
}

Try / catch

if err := rows.Err(); err != nil {
    if isTransientStreamErr(err, ctx) {
        // safe to retry the whole read-only query with backoff
    }
    return nil, fmt.Errorf("error iterating rows: %w", err)
}

Prevention

When it happens

Trigger: RunSQL iterating a large result set when the TCP connection drops, the ctx is cancelled, Firebird server restarts, or a sweep/garbage-collection issue kills the cursor server-side.

Common situations: Long-running queries over slow/unstable networks; context timeout shorter than full result fetch; firewalls killing idle connections mid-stream; very large result sets exceeding server limits.

Related errors


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