{"record":{"id":"97e2e4083477b187","repo":"tursodatabase/turso","slug":"size-must-be-non-negative","errorCode":null,"errorMessage":"size must be non-negative","messagePattern":"size must be non-negative","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"bindings/python/turso/lib.py","lineNumber":879,"sourceCode":"            except TypeError:\n                _reject_stdlib_row_factory(rf)\n                raise\n        # Fallback: return tuple\n        return row_values\n\n    def fetchone(self) -> Any:\n        self._ensure_open()\n        row = self._fetchone_tuple()\n        if row is None:\n            return None\n        return self._apply_row_factory(row)\n\n    def fetchmany(self, size: Optional[int] = None) -> list[Any]:\n        self._ensure_open()\n        if size is None:\n            size = self.arraysize\n        if size < 0:\n            raise ValueError(\"size must be non-negative\")\n        result: list[Any] = []\n        for _ in range(size):\n            row = self._fetchone_tuple()\n            if row is None:\n                break\n            result.append(self._apply_row_factory(row))\n        return result\n\n    def fetchall(self) -> list[Any]:\n        self._ensure_open()\n        result: list[Any] = []\n        while True:\n            row = self._fetchone_tuple()\n            if row is None:\n                break\n            result.append(self._apply_row_factory(row))\n        return result\n","sourceCodeStart":861,"sourceCodeEnd":897,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/python/turso/lib.py#L861-L897","documentation":"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.","triggerScenarios":"`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).","commonSituations":"Code ported from drivers where fetchmany(-1) means unlimited; paging loops with off-by-one arithmetic on remaining counts.","solutions":["Use fetchall() when you want every remaining row","Clamp computed sizes: cur.fetchmany(max(0, n))","Pass None (or rely on arraysize) for the default batch size"],"exampleFix":"# before\nrows = cur.fetchmany(remaining - 1)  # goes to -1 on the last page\n\n# after\nrows = cur.fetchmany(max(0, remaining - 1))\n# or, when draining everything:\nrows = cur.fetchall()","handlingStrategy":"validation","validationCode":"def safe_fetchmany(cur, size):\n    \"\"\"None -> arraysize; negative sizes are invalid, 0 returns [].\"\"\"\n    if size is None or size >= 0:\n        return cur.fetchmany(size)\n    return cur.fetchall()  # negative intent usually means 'everything remaining'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use fetchall() to drain remaining rows — never fetchmany(-1)","Clamp paging arithmetic with max(0, n)","Pass None to fall back to cursor.arraysize for the default batch"],"tags":["python","fetch","pagination","validation"],"backgroundTag":"invalid-fetch-size","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}