tursodatabase/turso · error · ValueError

size must be non-negative

Error message

size must be non-negative

What it means

Cursor.fetchmany(size) requires size >= 0; a negative value raises ValueError (plain Python, not a DB-API error). When size is None it defaults to cursor.arraysize, and size 0 legally returns an empty list. There is no negative-means-all convention here — use fetchall() for everything remaining.

Source

Thrown at bindings/python/turso/lib.py:879

            except TypeError:
                _reject_stdlib_row_factory(rf)
                raise
        # Fallback: return tuple
        return row_values

    def fetchone(self) -> Any:
        self._ensure_open()
        row = self._fetchone_tuple()
        if row is None:
            return None
        return self._apply_row_factory(row)

    def fetchmany(self, size: Optional[int] = None) -> list[Any]:
        self._ensure_open()
        if size is None:
            size = self.arraysize
        if size < 0:
            raise ValueError("size must be non-negative")
        result: list[Any] = []
        for _ in range(size):
            row = self._fetchone_tuple()
            if row is None:
                break
            result.append(self._apply_row_factory(row))
        return result

    def fetchall(self) -> list[Any]:
        self._ensure_open()
        result: list[Any] = []
        while True:
            row = self._fetchone_tuple()
            if row is None:
                break
            result.append(self._apply_row_factory(row))
        return result

View on GitHub (pinned to bad083fafb)

Solutions

  1. Use fetchall() when you want every remaining row
  2. Clamp computed sizes: cur.fetchmany(max(0, n))
  3. Pass None (or rely on arraysize) for the default batch size

Example fix

# before
rows = cur.fetchmany(remaining - 1)  # goes to -1 on the last page

# after
rows = cur.fetchmany(max(0, remaining - 1))
# or, when draining everything:
rows = cur.fetchall()
Defensive patterns

Strategy: validation

Validate before calling

def safe_fetchmany(cur, size):
    """None -> arraysize; negative sizes are invalid, 0 returns []."""
    if size is None or size >= 0:
        return cur.fetchmany(size)
    return cur.fetchall()  # negative intent usually means 'everything remaining'

Prevention

When it happens

Trigger: `cur.fetchmany(-1)` expecting all remaining rows (a convention some other DB-API drivers use), or a computed batch size that underflows to negative (e.g. rows_left going below zero in a paging loop).

Common situations: Code ported from drivers where fetchmany(-1) means unlimited; paging loops with off-by-one arithmetic on remaining counts.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/97e2e4083477b187. Report an issue: GitHub.