From 22ccd8a087903e4d04547fc33bf2d0b43d998520 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 23 Jun 2026 14:04:20 -0700 Subject: [PATCH] escape_sqlite() favors double quotes, closes #2795 --- datasette/utils/__init__.py | 7 +------ tests/test_utils.py | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 9174dca7..b4ede953 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -410,12 +410,7 @@ def escape_css_string(s): def escape_sqlite(s): if _boring_keyword_re.match(s) and (s.lower() not in reserved_words): return s - elif "]" in s: - # SQLite does not support escaping ] inside [bracket] quoting, so fall - # back to double-quote quoting (doubling any embedded ") - #2677 - return '"{}"'.format(s.replace('"', '""')) - else: - return f"[{s}]" + return '"{}"'.format(s.replace('"', '""')) def make_dockerfile( diff --git a/tests/test_utils.py b/tests/test_utils.py index 8d080a8a..38ebb51e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -221,11 +221,10 @@ def test_detect_fts(open_quote, close_quote): "identifier,expected", ( ("plain", "plain"), - ("select", "[select]"), - ("has space", "[has space]"), - ("has'quote", "[has'quote]"), - # Identifiers containing ] must fall back to double-quote quoting - # (SQLite does not support escaping ] inside [brackets]) - #2677 + ("select", '"select"'), + ("has space", '"has space"'), + ("has'quote", '"has\'quote"'), + ('has"dquote', '"has""dquote"'), ("has]bracket", '"has]bracket"'), ('has"dquote]', '"has""dquote]"'), ), @@ -234,6 +233,25 @@ def test_escape_sqlite(identifier, expected): assert utils.escape_sqlite(identifier) == expected +def test_escape_sqlite_double_quotes_work_in_query(): + conn = utils.sqlite3.connect(":memory:") + table = 'table with "double quotes"' + column = "select" + escaped_table = utils.escape_sqlite(table) + escaped_column = utils.escape_sqlite(column) + conn.execute(f"CREATE TABLE {escaped_table} ({escaped_column} TEXT)") + create_sql = conn.execute( + "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?", (table,) + ).fetchone()[0] + assert create_sql == 'CREATE TABLE "table with ""double quotes""" ("select" TEXT)' + conn.execute( + f"INSERT INTO {escaped_table} ({escaped_column}) VALUES (?)", ("hello",) + ) + results = conn.execute(f"SELECT {escaped_column} FROM {escaped_table}").fetchall() + conn.close() + assert results == [("hello",)] + + def test_escape_sqlite_prevents_injection(): # https://github.com/simonw/datasette/issues/2677 conn = utils.sqlite3.connect(":memory:")