mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-10-07 18:07:03 +02:00
where= and where_args= parameters to search() and search_sql()
Closes #441
This commit is contained in:
parent
0b6aba696d
commit
1b09538bc6
3 changed files with 104 additions and 4 deletions
|
|
@ -2108,10 +2108,16 @@ 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.
|
||||||
|
|
||||||
|
``where`` string
|
||||||
|
Extra SQL fragment for the WHERE clause
|
||||||
|
|
||||||
|
``where_args`` dictionary
|
||||||
|
Arguments to use for ``:param`` placeholders in the extra WHERE clause
|
||||||
|
|
||||||
``quote`` bool
|
``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.
|
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"`` where the ``id`` is greater than 10 ordered by ``published`` with the most recent first, use the following:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
@ -2119,6 +2125,8 @@ To return just the title and published columns for three matches for ``"dog"`` o
|
||||||
"dog",
|
"dog",
|
||||||
order_by="published desc",
|
order_by="published desc",
|
||||||
limit=3,
|
limit=3,
|
||||||
|
where="id > :min_id",
|
||||||
|
where_args={"min_id": 10},
|
||||||
columns=["title", "published"]
|
columns=["title", "published"]
|
||||||
):
|
):
|
||||||
print(article)
|
print(article)
|
||||||
|
|
@ -2128,7 +2136,7 @@ To return just the title and published columns for three matches for ``"dog"`` o
|
||||||
Building SQL queries with table.search_sql()
|
Building SQL queries with table.search_sql()
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
You can generate the SQL query that would be used for a search using the ``table.search_sql()`` method. It takes the same arguments as ``table.search()`` with the exception of the search query itself, since the returned SQL includes a parameter that can be used for the search.
|
You can generate the SQL query that would be used for a search using the ``table.search_sql()`` method. It takes the same arguments as ``table.search()``, with the exception of the search query and the ``where_args`` parameter, since those should be provided when the returned SQL is executed.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2254,6 +2254,7 @@ class Table(Queryable):
|
||||||
order_by: Optional[str] = None,
|
order_by: Optional[str] = None,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
offset: Optional[int] = None,
|
offset: Optional[int] = None,
|
||||||
|
where: Optional[str] = None,
|
||||||
) -> str:
|
) -> 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.
|
||||||
|
|
@ -2262,6 +2263,7 @@ class Table(Queryable):
|
||||||
:param order_by: Column or SQL expression to sort by
|
:param order_by: Column or SQL expression to sort by
|
||||||
:param limit: SQL limit
|
:param limit: SQL limit
|
||||||
:param offset: SQL offset
|
:param offset: SQL offset
|
||||||
|
:param where: Extra SQL fragment for the WHERE clause
|
||||||
"""
|
"""
|
||||||
# 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"
|
||||||
|
|
@ -2283,7 +2285,7 @@ class Table(Queryable):
|
||||||
select
|
select
|
||||||
rowid,
|
rowid,
|
||||||
{columns}
|
{columns}
|
||||||
from [{dbtable}]
|
from [{dbtable}]{where_clause}
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
{columns_with_prefix}
|
{columns_with_prefix}
|
||||||
|
|
@ -2311,6 +2313,7 @@ class Table(Queryable):
|
||||||
limit_offset += " offset {}".format(offset)
|
limit_offset += " offset {}".format(offset)
|
||||||
return sql.format(
|
return sql.format(
|
||||||
dbtable=self.name,
|
dbtable=self.name,
|
||||||
|
where_clause="\n where {}".format(where) if where else "",
|
||||||
original=original,
|
original=original,
|
||||||
columns=columns_sql,
|
columns=columns_sql,
|
||||||
columns_with_prefix=columns_with_prefix_sql,
|
columns_with_prefix=columns_with_prefix_sql,
|
||||||
|
|
@ -2326,6 +2329,8 @@ class Table(Queryable):
|
||||||
columns: Optional[Iterable[str]] = None,
|
columns: Optional[Iterable[str]] = None,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
offset: Optional[int] = None,
|
offset: Optional[int] = None,
|
||||||
|
where: str = None,
|
||||||
|
where_args: Optional[Union[Iterable, dict]] = None,
|
||||||
quote: bool = False,
|
quote: bool = False,
|
||||||
) -> Generator[dict, None, None]:
|
) -> Generator[dict, None, None]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -2337,18 +2342,29 @@ class Table(Queryable):
|
||||||
:param columns: List of columns to return, defaults to all columns.
|
:param columns: List of columns to return, defaults to all columns.
|
||||||
:param limit: Optional integer limit for returned rows.
|
:param limit: Optional integer limit for returned rows.
|
||||||
:param offset: Optional integer SQL offset.
|
:param offset: Optional integer SQL offset.
|
||||||
|
:param where: Extra SQL fragment for the WHERE clause
|
||||||
|
:param where_args: Arguments to use for :param placeholders in the extra WHERE clause
|
||||||
:param quote: Apply quoting to disable any special characters in the search query
|
:param quote: Apply quoting to disable any special characters in the search query
|
||||||
|
|
||||||
See :ref:`python_api_fts_search`.
|
See :ref:`python_api_fts_search`.
|
||||||
"""
|
"""
|
||||||
|
args = {"query": self.db.quote_fts(q) if quote else q}
|
||||||
|
if where_args and "query" in where_args:
|
||||||
|
raise ValueError(
|
||||||
|
"'query' is a reserved key and cannot be passed to where_args for .search()"
|
||||||
|
)
|
||||||
|
if where_args:
|
||||||
|
args.update(where_args)
|
||||||
|
|
||||||
cursor = self.db.execute(
|
cursor = self.db.execute(
|
||||||
self.search_sql(
|
self.search_sql(
|
||||||
order_by=order_by,
|
order_by=order_by,
|
||||||
columns=columns,
|
columns=columns,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
|
where=where,
|
||||||
),
|
),
|
||||||
{"query": self.db.quote_fts(q) if quote else q},
|
args,
|
||||||
)
|
)
|
||||||
columns = [c[0] for c in cursor.description]
|
columns = [c[0] for c in cursor.description]
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,38 @@ def test_search_limit_offset(fresh_db):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fts_version", ("FTS4", "FTS5"))
|
||||||
|
def test_search_where(fresh_db, fts_version):
|
||||||
|
table = fresh_db["t"]
|
||||||
|
table.insert_all(search_records)
|
||||||
|
table.enable_fts(["text", "country"], fts_version=fts_version)
|
||||||
|
results = list(
|
||||||
|
table.search("are", where="country = :country", where_args={"country": "Japan"})
|
||||||
|
)
|
||||||
|
assert results == [
|
||||||
|
{
|
||||||
|
"rowid": 1,
|
||||||
|
"text": "tanuki are running tricksters",
|
||||||
|
"country": "Japan",
|
||||||
|
"not_searchable": "foo",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_where_args_disallows_query(fresh_db):
|
||||||
|
table = fresh_db["t"]
|
||||||
|
with pytest.raises(ValueError) as ex:
|
||||||
|
list(
|
||||||
|
table.search(
|
||||||
|
"x", where="author = :query", where_args={"query": "not allowed"}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
ex.value.args[0]
|
||||||
|
== "'query' is a reserved key and cannot be passed to where_args for .search()"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_enable_fts_table_names_containing_spaces(fresh_db):
|
def test_enable_fts_table_names_containing_spaces(fresh_db):
|
||||||
table = fresh_db["test"]
|
table = fresh_db["test"]
|
||||||
table.insert({"column with spaces": "in its name"})
|
table.insert({"column with spaces": "in its name"})
|
||||||
|
|
@ -415,6 +447,28 @@ def test_enable_fts_error_message_on_views():
|
||||||
"limit 10"
|
"limit 10"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
{"where": "author = :author"},
|
||||||
|
"FTS5",
|
||||||
|
(
|
||||||
|
"with original as (\n"
|
||||||
|
" select\n"
|
||||||
|
" rowid,\n"
|
||||||
|
" *\n"
|
||||||
|
" from [books]\n"
|
||||||
|
" where author = :author\n"
|
||||||
|
")\n"
|
||||||
|
"select\n"
|
||||||
|
" [original].*\n"
|
||||||
|
"from\n"
|
||||||
|
" [original]\n"
|
||||||
|
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
||||||
|
"where\n"
|
||||||
|
" [books_fts] match :query\n"
|
||||||
|
"order by\n"
|
||||||
|
" [books_fts].rank"
|
||||||
|
),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
{"columns": ["title"]},
|
{"columns": ["title"]},
|
||||||
"FTS4",
|
"FTS4",
|
||||||
|
|
@ -480,6 +534,28 @@ def test_enable_fts_error_message_on_views():
|
||||||
"limit 2"
|
"limit 2"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
{"where": "author = :author"},
|
||||||
|
"FTS4",
|
||||||
|
(
|
||||||
|
"with original as (\n"
|
||||||
|
" select\n"
|
||||||
|
" rowid,\n"
|
||||||
|
" *\n"
|
||||||
|
" from [books]\n"
|
||||||
|
" where author = :author\n"
|
||||||
|
")\n"
|
||||||
|
"select\n"
|
||||||
|
" [original].*\n"
|
||||||
|
"from\n"
|
||||||
|
" [original]\n"
|
||||||
|
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
||||||
|
"where\n"
|
||||||
|
" [books_fts] match :query\n"
|
||||||
|
"order by\n"
|
||||||
|
" rank_bm25(matchinfo([books_fts], 'pcnalx'))"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_search_sql(kwargs, fts, expected):
|
def test_search_sql(kwargs, fts, expected):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue