Skip to content

Commit ccf128c

Browse files
committed
sqlite-utils search --quote option, closes #296
1 parent 8ae77a6 commit ccf128c

File tree

3 files changed

+48
-15
lines changed

3 files changed

+48
-15
lines changed

docs/cli.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,8 @@ By default it shows the most relevant matches first. You can specify a different
15191519
# Sort by created in descending order
15201520
$ sqlite-utils search mydb.db documents searchterm -o 'created desc'
15211521

1522+
SQLite `advanced search syntax <https://www.sqlite.org/fts5.html#full_text_query_syntax>`__ is enabled by default. To run a search with automatic quoting use the ``--quote`` option.
1523+
15221524
You can specify a subset of columns to be returned using the ``-c`` option one or more times::
15231525

15241526
$ sqlite-utils search mydb.db documents searchterm -c title -c created

sqlite_utils/cli.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,7 @@ def _execute_query(
13751375
@click.option(
13761376
"--sql", "show_sql", is_flag=True, help="Show SQL query that would be run"
13771377
)
1378+
@click.option("--quote", is_flag=True, help="Apply FTS quoting rules to search term")
13781379
@output_options
13791380
@load_extension_option
13801381
@click.pass_context
@@ -1385,6 +1386,7 @@ def search(
13851386
q,
13861387
order,
13871388
show_sql,
1389+
quote,
13881390
column,
13891391
limit,
13901392
nl,
@@ -1420,21 +1422,31 @@ def search(
14201422
if show_sql:
14211423
click.echo(sql)
14221424
return
1423-
ctx.invoke(
1424-
query,
1425-
path=path,
1426-
sql=sql,
1427-
nl=nl,
1428-
arrays=arrays,
1429-
csv=csv,
1430-
tsv=tsv,
1431-
no_headers=no_headers,
1432-
table=table,
1433-
fmt=fmt,
1434-
json_cols=json_cols,
1435-
param=[("query", q)],
1436-
load_extension=load_extension,
1437-
)
1425+
if quote:
1426+
q = db.quote_fts(q)
1427+
try:
1428+
ctx.invoke(
1429+
query,
1430+
path=path,
1431+
sql=sql,
1432+
nl=nl,
1433+
arrays=arrays,
1434+
csv=csv,
1435+
tsv=tsv,
1436+
no_headers=no_headers,
1437+
table=table,
1438+
fmt=fmt,
1439+
json_cols=json_cols,
1440+
param=[("query", q)],
1441+
load_extension=load_extension,
1442+
)
1443+
except click.ClickException as e:
1444+
if "malformed MATCH expression" in str(e) or "unterminated string" in str(e):
1445+
raise click.ClickException(
1446+
"{}\n\nTry running this again with the --quote option".format(str(e))
1447+
)
1448+
else:
1449+
raise
14381450

14391451

14401452
@cli.command()

tests/test_cli.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,25 @@ def test_search(tmpdir, fts, extra_arg, expected):
19941994
assert result.output.replace("\r", "") == expected
19951995

19961996

1997+
def test_search_quote(tmpdir):
1998+
db_path = str(tmpdir / "test.db")
1999+
db = Database(db_path)
2000+
db["creatures"].insert({"name": "dog."}).enable_fts(["name"])
2001+
# Without --quote should return an error
2002+
error_result = CliRunner().invoke(cli.cli, ["search", db_path, "creatures", 'dog"'])
2003+
assert error_result.exit_code == 1
2004+
assert error_result.output == (
2005+
"Error: unterminated string\n\n"
2006+
"Try running this again with the --quote option\n"
2007+
)
2008+
# With --quote it should work
2009+
result = CliRunner().invoke(
2010+
cli.cli, ["search", db_path, "creatures", 'dog"', "--quote"]
2011+
)
2012+
assert result.exit_code == 0
2013+
assert result.output.strip() == '[{"rowid": 1, "name": "dog."}]'
2014+
2015+
19972016
def test_indexes(tmpdir):
19982017
db_path = str(tmpdir / "test.db")
19992018
db = Database(db_path)

0 commit comments

Comments
 (0)