Skip RETURNING tests if SQLite version does not support it

https://github.com/simonw/datasette/pull/2763#issuecomment-4588138314
This commit is contained in:
Simon Willison 2026-05-31 15:34:01 -07:00
commit 58dcedb510
4 changed files with 59 additions and 2 deletions

View file

@ -13,6 +13,7 @@ if hasattr(sqlite3, "enable_callback_tracebacks"):
sqlite3.enable_callback_tracebacks(True)
_cached_sqlite_version = None
_cached_supports_returning = None
SQLiteTableType = Literal["table", "view", "virtual", "shadow"]
_VIRTUAL_TABLE_MODULE_RE = re.compile(
r"\bCREATE\s+VIRTUAL\s+TABLE\b.*?\bUSING\s+([^\s(]+)",
@ -59,6 +60,21 @@ def supports_generated_columns():
return sqlite_version() >= (3, 31, 0)
def supports_returning():
global _cached_supports_returning
if _cached_supports_returning is None:
conn = sqlite3.connect(":memory:")
try:
conn.execute("create table t (id integer primary key)")
conn.execute("insert into t default values returning id").fetchone()
_cached_supports_returning = True
except sqlite3.DatabaseError:
_cached_supports_returning = False
finally:
conn.close()
return _cached_supports_returning
def sqlite_table_type(
conn,
table: str,

View file

@ -8,12 +8,16 @@ from datasette.app import Datasette
from datasette.database import Database, ExecuteWriteResult, Results, MultipleValues
from datasette.database import DatasetteClosedError
from datasette.database import _deliver_write_result
from datasette.utils.sqlite import sqlite3
from datasette.utils.sqlite import sqlite3, supports_returning
from datasette.utils import Column
import pytest
import time
import uuid
requires_sqlite_returning = pytest.mark.skipif(
not supports_returning(), reason="SQLite does not support RETURNING"
)
@pytest.fixture
def db(app_client):
@ -481,6 +485,7 @@ async def test_execute_write_block_true(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning(db):
await db.execute_write(
"create table write_returning (id integer primary key, name text)"
@ -502,6 +507,7 @@ async def test_execute_write_with_returning(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning_default_limit(db):
await db.execute_write(
"create table write_returning_limit (id integer primary key)"
@ -524,6 +530,7 @@ async def test_execute_write_with_returning_default_limit(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning_custom_limit(db):
await db.execute_write(
"create table write_returning_custom (id integer primary key)"
@ -544,6 +551,7 @@ async def test_execute_write_with_returning_custom_limit(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning_exact_default_limit(db):
await db.execute_write(
"create table write_returning_exact_limit (id integer primary key)"
@ -563,6 +571,7 @@ async def test_execute_write_with_returning_exact_default_limit(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning_one_more_than_default_limit(db):
await db.execute_write(
"create table write_returning_one_more (id integer primary key)"
@ -582,6 +591,7 @@ async def test_execute_write_with_returning_one_more_than_default_limit(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning_return_all(db):
await db.execute_write("create table write_returning_all (id integer primary key)")
await db.execute_write_many(
@ -611,6 +621,7 @@ async def test_execute_write_block_false(db):
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_with_returning_block_false(db):
await db.execute_write(
"create table write_returning_block_false (id integer primary key, name text)"

View file

@ -8,6 +8,11 @@ from datasette.app import Datasette
from datasette.resources import DatabaseResource, QueryResource
from datasette.stored_queries import StoredQuery, StoredQueryPage
from datasette.utils.asgi import Forbidden
from datasette.utils.sqlite import supports_returning
requires_sqlite_returning = pytest.mark.skipif(
not supports_returning(), reason="SQLite does not support RETURNING"
)
def _template_option_attributes(html, table):
@ -1891,6 +1896,7 @@ async def test_execute_write_post_requires_database_and_table_permissions():
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_json_includes_returning_rows():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
@ -1921,6 +1927,7 @@ async def test_execute_write_json_includes_returning_rows():
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_json_returning_rows_can_be_truncated():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
@ -1953,6 +1960,7 @@ async def test_execute_write_json_returning_rows_can_be_truncated():
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_html_displays_returning_rows():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
@ -1990,6 +1998,7 @@ async def test_execute_write_html_displays_returning_rows():
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_execute_write_html_returning_rows_can_be_truncated():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
@ -3135,6 +3144,7 @@ async def test_user_writable_query_execution_rechecks_table_permissions():
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_stored_write_query_with_returning():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
@ -3164,6 +3174,7 @@ async def test_stored_write_query_with_returning():
@pytest.mark.asyncio
@requires_sqlite_returning
async def test_stored_write_query_with_truncated_returning_message():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True

View file

@ -5,7 +5,12 @@ Tests for various datasette helper functions.
from datasette.app import Datasette
from datasette import utils
from datasette.utils.asgi import Request
from datasette.utils.sqlite import sqlite3, sqlite_hidden_table_names, sqlite_table_type
from datasette.utils.sqlite import (
sqlite3,
sqlite_hidden_table_names,
sqlite_table_type,
supports_returning,
)
import json
import os
import pathlib
@ -226,6 +231,20 @@ def test_detect_fts_different_table_names(table):
conn.close()
def test_supports_returning():
conn = utils.sqlite3.connect(":memory:")
try:
conn.execute("create table t (id integer primary key)")
conn.execute("insert into t default values returning id").fetchone()
expected = True
except sqlite3.DatabaseError:
expected = False
finally:
conn.close()
assert supports_returning() is expected
@pytest.mark.parametrize("use_fallback", (False, True))
def test_sqlite_table_type_detects_virtual_and_shadow_tables(monkeypatch, use_fallback):
if use_fallback: