From 6cb65555f46456eb31b62e855e21b1d8c809b1a2 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 24 Feb 2020 21:56:03 -0800 Subject: [PATCH] ?_searchmode=raw option (#686) --- datasette/views/table.py | 15 +++++++++++---- docs/json_api.rst | 6 ++++++ tests/test_api.py | 13 +++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/datasette/views/table.py b/datasette/views/table.py index 140f2a15..4d439d24 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -356,13 +356,18 @@ class TableView(RowTableShared): pair for pair in special_args.items() if pair[0].startswith("_search") ) search = "" + search_mode_raw = special_args.get("_searchmode") == "raw" if fts_table and search_args: if "_search" in search_args: # Simple ?_search=xxx search = search_args["_search"] where_clauses.append( - "{fts_pk} in (select rowid from {fts_table} where {fts_table} match escape_fts(:search))".format( - fts_table=escape_sqlite(fts_table), fts_pk=escape_sqlite(fts_pk) + "{fts_pk} in (select rowid from {fts_table} where {fts_table} match {match_clause})".format( + fts_table=escape_sqlite(fts_table), + fts_pk=escape_sqlite(fts_pk), + match_clause=":search" + if search_mode_raw + else "escape_fts(:search)", ) ) extra_human_descriptions.append('search matches "{}"'.format(search)) @@ -375,10 +380,12 @@ class TableView(RowTableShared): raise DatasetteError("Cannot search by that column", status=400) where_clauses.append( - "rowid in (select rowid from {fts_table} where {search_col} match escape_fts(:search_{i}))".format( + "rowid in (select rowid from {fts_table} where {search_col} match {match_clause})".format( fts_table=escape_sqlite(fts_table), search_col=escape_sqlite(search_col), - i=i, + match_clause=":search_{}".format(i) + if search_mode_raw + else "escape_fts(:search_{})".format(i), ) ) extra_human_descriptions.append( diff --git a/docs/json_api.rst b/docs/json_api.rst index e369bee7..d40f8956 100644 --- a/docs/json_api.rst +++ b/docs/json_api.rst @@ -281,6 +281,12 @@ Special table arguments Like ``_search=`` but allows you to specify the column to be searched, as opposed to searching all columns that have been indexed by FTS. +``?_searchmode=raw`` + With this option, queries passed to ``?_search=`` or ``?_search_COLUMN=`` will + not have special characters escaped. This means you can make use of the full + set of `advanced SQLite FTS syntax `__, + though this could potentially result in errors if the wrong syntax is used. + ``?_where=SQL-fragment`` If the :ref:`config_allow_sql` config option is enabled, this parameter can be used to pass one or more additional SQL fragments to be used in the diff --git a/tests/test_api.py b/tests/test_api.py index 27fa26bb..90cc77bb 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -952,6 +952,19 @@ def test_sortable_columns_metadata(app_client): "/fixtures/searchable.json?_search=AND", [], ), + ( + # Without _searchmode=raw this should return no results + "/fixtures/searchable.json?_search=te*+AND+do*", + [], + ), + ( + # _searchmode=raw + "/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw", + [ + [1, "barry cat", "terry dog", "panther"], + [2, "terry dog", "sara weasel", "puma"], + ], + ), ( "/fixtures/searchable.json?_search=weasel", [[2, "terry dog", "sara weasel", "puma"]],