diff --git a/docs/cli.rst b/docs/cli.rst index 0c08526..6a96a82 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -1519,6 +1519,8 @@ By default it shows the most relevant matches first. You can specify a different # Sort by created in descending order $ sqlite-utils search mydb.db documents searchterm -o 'created desc' +SQLite `advanced search syntax `__ is enabled by default. To run a search with automatic quoting use the ``--quote`` option. + You can specify a subset of columns to be returned using the ``-c`` option one or more times:: $ sqlite-utils search mydb.db documents searchterm -c title -c created diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index d94229d..4b39a5e 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -1375,6 +1375,7 @@ def _execute_query( @click.option( "--sql", "show_sql", is_flag=True, help="Show SQL query that would be run" ) +@click.option("--quote", is_flag=True, help="Apply FTS quoting rules to search term") @output_options @load_extension_option @click.pass_context @@ -1385,6 +1386,7 @@ def search( q, order, show_sql, + quote, column, limit, nl, @@ -1420,21 +1422,31 @@ def search( if show_sql: click.echo(sql) return - ctx.invoke( - query, - path=path, - sql=sql, - nl=nl, - arrays=arrays, - csv=csv, - tsv=tsv, - no_headers=no_headers, - table=table, - fmt=fmt, - json_cols=json_cols, - param=[("query", q)], - load_extension=load_extension, - ) + if quote: + q = db.quote_fts(q) + try: + ctx.invoke( + query, + path=path, + sql=sql, + nl=nl, + arrays=arrays, + csv=csv, + tsv=tsv, + no_headers=no_headers, + table=table, + fmt=fmt, + json_cols=json_cols, + param=[("query", q)], + load_extension=load_extension, + ) + except click.ClickException as e: + if "malformed MATCH expression" in str(e) or "unterminated string" in str(e): + raise click.ClickException( + "{}\n\nTry running this again with the --quote option".format(str(e)) + ) + else: + raise @cli.command() diff --git a/tests/test_cli.py b/tests/test_cli.py index d4801ac..58347cd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1994,6 +1994,25 @@ def test_search(tmpdir, fts, extra_arg, expected): assert result.output.replace("\r", "") == expected +def test_search_quote(tmpdir): + db_path = str(tmpdir / "test.db") + db = Database(db_path) + db["creatures"].insert({"name": "dog."}).enable_fts(["name"]) + # Without --quote should return an error + error_result = CliRunner().invoke(cli.cli, ["search", db_path, "creatures", 'dog"']) + assert error_result.exit_code == 1 + assert error_result.output == ( + "Error: unterminated string\n\n" + "Try running this again with the --quote option\n" + ) + # With --quote it should work + result = CliRunner().invoke( + cli.cli, ["search", db_path, "creatures", 'dog"', "--quote"] + ) + assert result.exit_code == 0 + assert result.output.strip() == '[{"rowid": 1, "name": "dog."}]' + + def test_indexes(tmpdir): db_path = str(tmpdir / "test.db") db = Database(db_path)