From 2d2376ffd820d673ce97c410f24c06bbc6d59a98 Mon Sep 17 00:00:00 2001 From: Mark Neumann Date: Tue, 16 Mar 2021 11:15:08 +0000 Subject: [PATCH] add escape functionality from datsette --- sqlite_utils/db.py | 21 +++++++++++++++++++++ tests/test_fts.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index d9a6b26..7c59c9c 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -18,6 +18,8 @@ import uuid SQLITE_MAX_VARS = 999 +_quote_fts_re = re.compile(r'\s+|(".*?")') + _virtual_table_using_re = re.compile( r""" ^ # Start of string @@ -254,6 +256,25 @@ class Database: {"value": value}, ).fetchone()[0] + + def quote_fts(self, query): + # NOTE: This is not a query validator for FTS. Sqlite has + # a well defined query syntax here: + # https://www2.sqlite.org/fts5.html#full_text_query_syntax + # but this function just aggressively quotes strings + # to ensure that they are valid. In particular, passing + # queries which make use of the query syntax will be incorrect, + # e.g 'NEAR(one, two, 3)'. + + # If query has unbalanced ", add one at end + if query.count('"') % 2: + query += '"' + bits = _quote_fts_re.split(query) + bits = [b for b in bits if b and b != '""'] + return " ".join( + '"{}"'.format(bit) if not bit.startswith('"') else bit for bit in bits + ) + def table_names(self, fts4=False, fts5=False): where = ["type = 'table'"] if fts4: diff --git a/tests/test_fts.py b/tests/test_fts.py index 9b6e43e..defff9d 100644 --- a/tests/test_fts.py +++ b/tests/test_fts.py @@ -509,6 +509,6 @@ def test_quote_fts_query(fresh_db): table.enable_fts(["text", "country"]) query = "cat's" - list(table.search(query)) - - \ No newline at end of file + result = fresh_db.quote_fts(query) + # Executing query does not crash. + list(table.search(result))