{"record":{"id":"b6e46f130515f032","repo":"tursodatabase/turso","slug":"no-sql-statements-to-execute","errorCode":null,"errorMessage":"no SQL statements to execute","messagePattern":"no SQL statements to execute","errorType":"exception","errorClass":"ProgrammingError","httpStatus":null,"severity":"error","filePath":"bindings/python/turso/lib.py","lineNumber":331,"sourceCode":"        \"\"\"\n        try:\n            stmt = self._conn.prepare_single(sql)\n            _run_execute_with_io(stmt, self.extra_io)\n            # finalize to ensure completion; finalize never mixes with execute\n            stmt.finalize()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def _prepare_first(self, sql: str) -> _Prepared:\n        \"\"\"\n        Prepare the first statement in the given SQL string and return metadata.\n        \"\"\"\n        try:\n            opt = self._conn.prepare_first(sql)\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n        if opt is None:\n            raise ProgrammingError(\"no SQL statements to execute\")\n\n        stmt, tail_idx = opt\n        # Determine whether statement returns columns (rows)\n        try:\n            columns = tuple(stmt.columns())\n        except Exception as exc:  # noqa: BLE001\n            # Clean up statement before re-raising\n            try:\n                stmt.finalize()\n            except Exception:\n                pass\n            raise _map_turso_exception(exc)\n        has_cols = len(columns) > 0\n        return _Prepared(stmt=stmt, tail_index=tail_idx, has_columns=has_cols, column_names=columns)\n\n    def _raise_if_multiple_statements(self, sql: str, tail_index: int) -> None:\n        \"\"\"\n        Ensure there is no second statement after the first one; otherwise raise ProgrammingError.","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/python/turso/lib.py#L313-L349","documentation":"Connection._prepare_first calls the native prepare_first, which returns None when the SQL string contains no statement — only whitespace and/or comments. In that case the wrapper raises DB-API ProgrammingError(\"no SQL statements to execute\") instead of silently doing nothing, so empty SQL is surfaced as a bug in the caller.","triggerScenarios":"`cur.execute(\"\")`, `cur.execute(\"   \")`, `cur.execute(\"-- only a comment\")`, `cur.execute(\"/* nothing */\")`, or execute() of dynamically built SQL whose fragments concatenated to an empty/comment-only string.","commonSituations":"SQL builder/template code that conditionally appends clauses and ends up empty; input filtering that strips every clause; leftover debug placeholders; config-driven SQL where the config omitted the statement.","solutions":["Guard before executing: skip when the stripped SQL is empty or contains only comments","Log the exact SQL string when this fires so the builder bug is obvious","Fix the builder so it always produces at least one real statement, or make the empty case an explicit no-op in your own code"],"exampleFix":"# before\nsql = build_query(filters)  # may return \"\"\ncur.execute(sql)  # ProgrammingError: no SQL statements to execute\n\n# after\nsql = build_query(filters)\nif sql.strip():\n    cur.execute(sql)","handlingStrategy":"validation","validationCode":"import re\n\ndef has_statement(sql: str) -> bool:\n    \"\"\"True if sql contains at least one real statement (not only whitespace/comments).\"\"\"\n    no_line = re.sub(r\"--[^\\n]*\", \"\", sql)\n    no_block = re.sub(r\"/\\*.*?\\*/\", \"\", no_block, flags=re.S)\n    return bool(no_block.strip())\n\nif has_statement(sql):\n    cur.execute(sql)","typeGuard":null,"tryCatchPattern":"try:\n    cur.execute(sql)\nexcept ProgrammingError as e:\n    if str(e) == \"no SQL statements to execute\":\n        logger.warning(\"empty SQL produced by builder: %r\", sql)\n        return []\n    raise","preventionTips":["Unit-test SQL builders with the empty-filter case and assert they either emit a statement or signal no-op","Log the exact SQL on this error — it always indicates a builder/config bug","Treat comment-only strings as no-ops explicitly in your own layer"],"tags":["python","sql","validation","db-api","empty-input"],"backgroundTag":"empty-sql-statement","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}