tursodatabase/turso · error · ValueError

size must be non-negative

Error message

size must be non-negative

What it means

Cursor.fetchmany(size) raises ValueError when size is negative. When size is None it falls back to cursor.arraysize, which matches the stdlib sqlite3 behavior. The check exists only for negative numbers; zero is legal and returns an empty list.

Solutions

  1. Call fetchall() when you want every remaining row
  2. Clamp the computed size: cur.fetchmany(max(0, size))
  3. Fix the pagination arithmetic so remaining rows never go negative

Example fix

# before
rows = cur.fetchmany(limit - fetched_so_far)  # -5 on the last page

# after
rows = cur.fetchmany(max(0, limit - fetched_so_far))
Defensive patterns

Strategy: validation

Validate before calling

def fetchmany_safe(cur, size):
    if size is None:
        return cur.fetchmany()
    if size < 0:
        raise ValueError("size must be >= 0; use fetchall() for all rows")
    return cur.fetchmany(size)

Try / catch

try:
    rows = cur.fetchmany(size)
except ValueError:
    rows = cur.fetchall()  # negative size usually means 'everything' was intended

Prevention

When it happens

Trigger: Calling cur.fetchmany(-1), often intending 'give me everything' as some other APIs allow; a size computed from pagination arithmetic such as limit - already_fetched that underflows to negative.

Common situations: Developers coming from APIs where -1 means 'all rows'; pagination code whose remaining-count math goes negative at the last page.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

            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 c1e5928725)