mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-10-07 18:07:03 +02:00
--where and -p options for sqlite-utils convert, closes #304
This commit is contained in:
parent
69c7da5ec9
commit
d83b256813
3 changed files with 111 additions and 4 deletions
11
docs/cli.rst
11
docs/cli.rst
|
|
@ -945,6 +945,17 @@ You can specify Python modules that should be imported and made available to you
|
||||||
'"\n".join(textwrap.wrap(value, 10))' \
|
'"\n".join(textwrap.wrap(value, 10))' \
|
||||||
--import=textwrap
|
--import=textwrap
|
||||||
|
|
||||||
|
The transformation will be applied to every row in the specified table. You can limit that to just rows that match a ``WHERE`` clause using ``--where``::
|
||||||
|
|
||||||
|
$ sqlite-utils convert content.db articles headline 'value.upper()' \
|
||||||
|
--where "headline like '%cat%'"
|
||||||
|
|
||||||
|
You can include named parameters in your where clause and populate them using one or more ``--param`` options::
|
||||||
|
|
||||||
|
$ sqlite-utils convert content.db articles headline 'value.upper()' \
|
||||||
|
--where "headline like :like" \
|
||||||
|
--param like '%cat%'
|
||||||
|
|
||||||
The ``--dry-run`` option will output a preview of the conversion against the first ten rows, without modifying the database.
|
The ``--dry-run`` option will output a preview of the conversion against the first ten rows, without modifying the database.
|
||||||
|
|
||||||
.. _cli_convert_recipes:
|
.. _cli_convert_recipes:
|
||||||
|
|
|
||||||
|
|
@ -1959,6 +1959,14 @@ def _generate_convert_help():
|
||||||
@click.option(
|
@click.option(
|
||||||
"--multi", is_flag=True, help="Populate columns for keys in returned dictionary"
|
"--multi", is_flag=True, help="Populate columns for keys in returned dictionary"
|
||||||
)
|
)
|
||||||
|
@click.option("--where", help="Optional where clause")
|
||||||
|
@click.option(
|
||||||
|
"-p",
|
||||||
|
"--param",
|
||||||
|
multiple=True,
|
||||||
|
type=(str, str),
|
||||||
|
help="Named :parameters for where clause",
|
||||||
|
)
|
||||||
@click.option("--output", help="Optional separate column to populate with the output")
|
@click.option("--output", help="Optional separate column to populate with the output")
|
||||||
@click.option(
|
@click.option(
|
||||||
"--output-type",
|
"--output-type",
|
||||||
|
|
@ -1976,6 +1984,8 @@ def convert(
|
||||||
imports,
|
imports,
|
||||||
dry_run,
|
dry_run,
|
||||||
multi,
|
multi,
|
||||||
|
where,
|
||||||
|
param,
|
||||||
output,
|
output,
|
||||||
output_type,
|
output_type,
|
||||||
drop,
|
drop,
|
||||||
|
|
@ -1992,6 +2002,7 @@ def convert(
|
||||||
# If single line and no 'return', add the return
|
# If single line and no 'return', add the return
|
||||||
if "\n" not in code and not code.strip().startswith("return "):
|
if "\n" not in code and not code.strip().startswith("return "):
|
||||||
code = "return {}".format(code)
|
code = "return {}".format(code)
|
||||||
|
where_args = dict(param) if param else []
|
||||||
# Compile the code into a function body called fn(value)
|
# Compile the code into a function body called fn(value)
|
||||||
new_code = ["def fn(value):"]
|
new_code = ["def fn(value):"]
|
||||||
for line in code.split("\n"):
|
for line in code.split("\n"):
|
||||||
|
|
@ -2010,20 +2021,29 @@ def convert(
|
||||||
select
|
select
|
||||||
[{column}] as value,
|
[{column}] as value,
|
||||||
preview_transform([{column}]) as preview
|
preview_transform([{column}]) as preview
|
||||||
from [{table}] limit 10
|
from [{table}]{where} limit 10
|
||||||
""".format(
|
""".format(
|
||||||
column=columns[0], table=table
|
column=columns[0],
|
||||||
|
table=table,
|
||||||
|
where=" where {}".format(where) if where is not None else "",
|
||||||
)
|
)
|
||||||
for row in db.conn.execute(sql).fetchall():
|
for row in db.conn.execute(sql, where_args).fetchall():
|
||||||
click.echo(str(row[0]))
|
click.echo(str(row[0]))
|
||||||
click.echo(" --- becomes:")
|
click.echo(" --- becomes:")
|
||||||
click.echo(str(row[1]))
|
click.echo(str(row[1]))
|
||||||
click.echo()
|
click.echo()
|
||||||
|
count = db[table].count_where(
|
||||||
|
where=where,
|
||||||
|
where_args=where_args,
|
||||||
|
)
|
||||||
|
click.echo("Would affect {} row{}".format(count, "" if count == 1 else "s"))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
db[table].convert(
|
db[table].convert(
|
||||||
columns,
|
columns,
|
||||||
fn,
|
fn,
|
||||||
|
where=where,
|
||||||
|
where_args=where_args,
|
||||||
output=output,
|
output=output,
|
||||||
output_type=output_type,
|
output_type=output_type,
|
||||||
drop=drop,
|
drop=drop,
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,8 @@ def test_convert_dryrun(test_db_and_path):
|
||||||
"\n"
|
"\n"
|
||||||
"None\n"
|
"None\n"
|
||||||
" --- becomes:\n"
|
" --- becomes:\n"
|
||||||
"None"
|
"None\n\n"
|
||||||
|
"Would affect 4 rows"
|
||||||
)
|
)
|
||||||
# But it should not have actually modified the table data
|
# But it should not have actually modified the table data
|
||||||
assert list(db["example"].rows) == [
|
assert list(db["example"].rows) == [
|
||||||
|
|
@ -133,6 +134,27 @@ def test_convert_dryrun(test_db_and_path):
|
||||||
{"id": 3, "dt": ""},
|
{"id": 3, "dt": ""},
|
||||||
{"id": 4, "dt": None},
|
{"id": 4, "dt": None},
|
||||||
]
|
]
|
||||||
|
# Test with a where clause too
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
cli.cli,
|
||||||
|
[
|
||||||
|
"convert",
|
||||||
|
db_path,
|
||||||
|
"example",
|
||||||
|
"dt",
|
||||||
|
"return re.sub('O..', 'OXX', value)",
|
||||||
|
"--import",
|
||||||
|
"re",
|
||||||
|
"--dry-run",
|
||||||
|
"--where",
|
||||||
|
"id = :id",
|
||||||
|
"-p",
|
||||||
|
"id",
|
||||||
|
"4",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output.strip().split("\n")[-1] == "Would affect 1 row"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("drop", (True, False))
|
@pytest.mark.parametrize("drop", (True, False))
|
||||||
|
|
@ -439,3 +461,57 @@ def test_multi_with_bad_function(test_db_and_path):
|
||||||
result = CliRunner().invoke(cli.cli, args)
|
result = CliRunner().invoke(cli.cli, args)
|
||||||
assert result.exit_code == 1, result.output
|
assert result.exit_code == 1, result.output
|
||||||
assert "When using --multi code must return a Python dictionary" in result.output
|
assert "When using --multi code must return a Python dictionary" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert_where(test_db_and_path):
|
||||||
|
db, db_path = test_db_and_path
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
cli.cli,
|
||||||
|
[
|
||||||
|
"convert",
|
||||||
|
db_path,
|
||||||
|
"example",
|
||||||
|
"dt",
|
||||||
|
"str(value).upper()",
|
||||||
|
"--where",
|
||||||
|
"id = :id",
|
||||||
|
"-p",
|
||||||
|
"id",
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert list(db["example"].rows) == [
|
||||||
|
{"id": 1, "dt": "5th October 2019 12:04"},
|
||||||
|
{"id": 2, "dt": "6TH OCTOBER 2019 00:05:06"},
|
||||||
|
{"id": 3, "dt": ""},
|
||||||
|
{"id": 4, "dt": None},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert_where_multi(fresh_db_and_path):
|
||||||
|
db, db_path = fresh_db_and_path
|
||||||
|
db["names"].insert_all(
|
||||||
|
[{"id": 1, "name": "Cleo"}, {"id": 2, "name": "Bants"}], pk="id"
|
||||||
|
)
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
cli.cli,
|
||||||
|
[
|
||||||
|
"convert",
|
||||||
|
db_path,
|
||||||
|
"names",
|
||||||
|
"name",
|
||||||
|
'{"upper": value.upper()}',
|
||||||
|
"--where",
|
||||||
|
"id = :id",
|
||||||
|
"-p",
|
||||||
|
"id",
|
||||||
|
2,
|
||||||
|
"--multi",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert 0 == result.exit_code, result.output
|
||||||
|
assert list(db["names"].rows) == [
|
||||||
|
{"id": 1, "name": "Cleo", "upper": None},
|
||||||
|
{"id": 2, "name": "Bants", "upper": "BANTS"},
|
||||||
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue