mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-09-28 04:44:26 +02:00
table.search(quote=True) parameter, refs #296
This commit is contained in:
parent
f0fd19267f
commit
8ae77a6961
3 changed files with 54 additions and 11 deletions
|
|
@ -1843,8 +1843,15 @@ The ``.has_counts_triggers`` property shows if a table has been configured with
|
||||||
|
|
||||||
.. _python_api_fts:
|
.. _python_api_fts:
|
||||||
|
|
||||||
Enabling full-text search
|
Full-text search
|
||||||
=========================
|
================
|
||||||
|
|
||||||
|
SQLite includes bundled extensions that implement `powerful full-text search <https://www.sqlite.org/fts5.html>`__.
|
||||||
|
|
||||||
|
.. _python_api_fts_enable:
|
||||||
|
|
||||||
|
Enabling full-text search for a table
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
You can enable full-text search on a table using ``.enable_fts(columns)``:
|
You can enable full-text search on a table using ``.enable_fts(columns)``:
|
||||||
|
|
||||||
|
|
@ -1947,6 +1954,9 @@ The ``.search()`` method also accepts the following optional parameters:
|
||||||
``offset`` integer
|
``offset`` integer
|
||||||
Offset to use along side the limit parameter.
|
Offset to use along side the limit parameter.
|
||||||
|
|
||||||
|
``quote`` bool
|
||||||
|
Apply :ref:`FTS quoting rules <python_api_quote_fts>` to the search query, disabling advanced query syntax in a way that avoids surprising errors.
|
||||||
|
|
||||||
To return just the title and published columns for three matches for ``"dog"`` ordered by ``published`` with the most recent first, use the following:
|
To return just the title and published columns for three matches for ``"dog"`` ordered by ``published`` with the most recent first, use the following:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
|
||||||
|
|
@ -1924,7 +1924,13 @@ class Table(Queryable):
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def search_sql(self, columns=None, order_by=None, limit=None, offset=None) -> str:
|
def search_sql(
|
||||||
|
self,
|
||||||
|
columns: Optional[Iterable[str]] = None,
|
||||||
|
order_by: Optional[str] = None,
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
offset: Optional[int] = None,
|
||||||
|
) -> str:
|
||||||
"Return SQL string that can be used to execute searches against this table."
|
"Return SQL string that can be used to execute searches against this table."
|
||||||
# Pick names for table and rank column that don't clash
|
# Pick names for table and rank column that don't clash
|
||||||
original = "original_" if self.name == "original" else "original"
|
original = "original_" if self.name == "original" else "original"
|
||||||
|
|
@ -1986,19 +1992,21 @@ class Table(Queryable):
|
||||||
self,
|
self,
|
||||||
q: str,
|
q: str,
|
||||||
order_by: Optional[str] = None,
|
order_by: Optional[str] = None,
|
||||||
columns: Optional[List[str]] = None,
|
columns: Optional[Iterable[str]] = None,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
offset: Optional[int] = None,
|
offset: Optional[int] = None,
|
||||||
|
quote: bool = False,
|
||||||
) -> Generator[dict, None, None]:
|
) -> Generator[dict, None, None]:
|
||||||
"""
|
"""
|
||||||
Execute a search against this table using SQLite full-text search, returning a sequence of
|
Execute a search against this table using SQLite full-text search, returning a sequence of
|
||||||
dictionaries for each row.
|
dictionaries for each row.
|
||||||
|
|
||||||
- ``q`` - words to search for
|
- ``q`` - terms to search for
|
||||||
- ``order_by`` - defaults to order by rank, or specify a column here.
|
- ``order_by`` - defaults to order by rank, or specify a column here.
|
||||||
- ``columns`` - list of columns to return, defaults to all columns.
|
- ``columns`` - list of columns to return, defaults to all columns.
|
||||||
- ``limit`` - optional integer limit for returned rows.
|
- ``limit`` - optional integer limit for returned rows.
|
||||||
- ``offset`` - optional integer SQL offset.
|
- ``offset`` - optional integer SQL offset.
|
||||||
|
- ``quote`` - apply quoting to disable any special characters in the search query
|
||||||
|
|
||||||
See :ref:`python_api_fts_search`.
|
See :ref:`python_api_fts_search`.
|
||||||
"""
|
"""
|
||||||
|
|
@ -2009,7 +2017,7 @@ class Table(Queryable):
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
),
|
),
|
||||||
{"query": q},
|
{"query": self.db.quote_fts(q) if quote else q},
|
||||||
)
|
)
|
||||||
columns = [c[0] for c in cursor.description]
|
columns = [c[0] for c in cursor.description]
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
|
|
|
||||||
|
|
@ -503,13 +503,38 @@ def test_search_sql(kwargs, fts, expected):
|
||||||
assert sql == expected
|
assert sql == expected
|
||||||
|
|
||||||
|
|
||||||
def test_quote_fts_query(fresh_db):
|
@pytest.mark.parametrize(
|
||||||
|
"input,expected",
|
||||||
|
(
|
||||||
|
("dog", '"dog"'),
|
||||||
|
("cat,", '"cat,"'),
|
||||||
|
("cat's", '"cat\'s"'),
|
||||||
|
("dog.", '"dog."'),
|
||||||
|
("cat dog", '"cat" "dog"'),
|
||||||
|
# If a phrase is already double quoted, leave it so
|
||||||
|
('"cat dog"', '"cat dog"'),
|
||||||
|
('"cat dog" fish', '"cat dog" "fish"'),
|
||||||
|
# Sensibly handle unbalanced double quotes
|
||||||
|
('cat"', '"cat"'),
|
||||||
|
('"cat dog" "fish', '"cat dog" "fish"'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_quote_fts_query(fresh_db, input, expected):
|
||||||
table = fresh_db["searchable"]
|
table = fresh_db["searchable"]
|
||||||
table.insert_all(search_records)
|
table.insert_all(search_records)
|
||||||
table.enable_fts(["text", "country"])
|
table.enable_fts(["text", "country"])
|
||||||
|
quoted = fresh_db.quote_fts(input)
|
||||||
query = "cat's"
|
assert quoted == expected
|
||||||
result = fresh_db.quote_fts(query)
|
|
||||||
# Executing query does not crash.
|
# Executing query does not crash.
|
||||||
list(table.search(result))
|
list(table.search(quoted))
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_quote(fresh_db):
|
||||||
|
table = fresh_db["searchable"]
|
||||||
|
table.insert_all(search_records)
|
||||||
|
table.enable_fts(["text", "country"])
|
||||||
|
query = "cat's"
|
||||||
|
with pytest.raises(sqlite3.OperationalError):
|
||||||
|
list(table.search(query))
|
||||||
|
# No exception with quote=True
|
||||||
|
list(table.search(query, quote=True))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue