From 9050f590846fe7802d28f99e887174474ca3cbcb Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Sat, 24 Jan 2026 12:18:26 -0500 Subject: [PATCH] accept multiple --execute options and run each specified query in turn. The current behavior is to accept multiple --execute options, but to only execute the last one: a bug. Since there are multiple values, we can also support --throttle, per the discussion in https://github.com/dbcli/mycli/pull/1460. One inconsistency between STDIN and --execute is that --execute does not support warning on destructive queries. This should probably be resolved in the direction of removing the warning from scripts on STDIN. --- changelog.md | 8 +++++ mycli/main.py | 75 ++++++++++++++++++++++------------------------- test/test_main.py | 15 ++++++++++ 3 files changed, 58 insertions(+), 40 deletions(-) diff --git a/changelog.md b/changelog.md index c8839328..2e011aac 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +TBD +============== + +Features +-------- +* Accept multiple `--execute` arguments, running each query in turn. + + 1.47.0 (2026/01/24) ============== diff --git a/mycli/main.py b/mycli/main.py index 006c7f69..90c438fd 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -1534,7 +1534,7 @@ def get_last_query(self) -> str | None: @click.option("--warn/--no-warn", default=None, help="Warn before running a destructive query.") @click.option("--local-infile", type=bool, help="Enable/disable LOAD DATA LOCAL INFILE.") @click.option("-g", "--login-path", type=str, help="Read this path from the login file.") -@click.option("-e", "--execute", type=str, help="Execute command and quit.") +@click.option("-e", "--execute", type=str, multiple=True, help="Execute command and quit.") @click.option("--init-command", type=str, help="SQL statement to execute after connecting.") @click.option( "--unbuffered", is_flag=True, help="Instead of copying every row of data into a buffer, fetch rows as needed, to save memory." @@ -1874,27 +1874,18 @@ def cli( # --execute argument if execute: - try: - if batch_format == 'csv': - mycli.main_formatter.format_name = 'csv' - if execute.endswith(r'\G'): - execute = execute[:-2] - elif batch_format == 'tsv': - mycli.main_formatter.format_name = 'tsv' - if execute.endswith(r'\G'): - execute = execute[:-2] - elif batch_format == 'table': - mycli.main_formatter.format_name = 'ascii' - if execute.endswith(r'\G'): - execute = execute[:-2] - else: - mycli.main_formatter.format_name = 'tsv' - - mycli.run_query(execute, checkpoint=checkpoint) - sys.exit(0) - except Exception as e: - click.secho(str(e), err=True, fg="red") - sys.exit(1) + counter = 0 + for query in execute: + set_batch_formatter(mycli, batch_format, counter) + counter += 1 + try: + if throttle and counter > 1: + sleep(throttle) + mycli.run_query(query, checkpoint=checkpoint) + except Exception as e: + click.secho(str(e), err=True, fg="red") + sys.exit(1) + sys.exit(0) if sys.stdin.isatty(): mycli.run_cli() @@ -1902,24 +1893,7 @@ def cli( stdin = click.get_text_stream("stdin") counter = 0 for stdin_text in stdin: - if counter: - if batch_format == 'csv': - mycli.main_formatter.format_name = 'csv-noheader' - elif batch_format == 'tsv': - mycli.main_formatter.format_name = 'tsv_noheader' - elif batch_format == 'table': - mycli.main_formatter.format_name = 'ascii' - else: - mycli.main_formatter.format_name = 'tsv' - else: - if batch_format == 'csv': - mycli.main_formatter.format_name = 'csv' - elif batch_format == 'tsv': - mycli.main_formatter.format_name = 'tsv' - elif batch_format == 'table': - mycli.main_formatter.format_name = 'ascii' - else: - mycli.main_formatter.format_name = 'tsv' + set_batch_formatter(mycli, batch_format, counter) counter += 1 warn_confirmed: bool | None = True if not noninteractive and mycli.destructive_warning and is_destructive(mycli.destructive_keywords, stdin_text): @@ -1943,6 +1917,27 @@ def cli( mycli.close() +def set_batch_formatter(mycli: MyCli, batch_format: str, counter: int) -> None: + if counter: + if batch_format == 'csv': + mycli.main_formatter.format_name = 'csv-noheader' + elif batch_format == 'tsv': + mycli.main_formatter.format_name = 'tsv_noheader' + elif batch_format == 'table': + mycli.main_formatter.format_name = 'ascii' + else: + mycli.main_formatter.format_name = 'tsv' + else: + if batch_format == 'csv': + mycli.main_formatter.format_name = 'csv' + elif batch_format == 'tsv': + mycli.main_formatter.format_name = 'tsv' + elif batch_format == 'table': + mycli.main_formatter.format_name = 'ascii' + else: + mycli.main_formatter.format_name = 'tsv' + + def need_completion_refresh(queries: str) -> bool: """Determines if the completion needs a refresh by checking if the sql statement is an alter, create, drop or change db.""" diff --git a/test/test_main.py b/test/test_main.py index 66a2ef85..81539651 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -346,6 +346,21 @@ def test_execute_arg(executor): assert expected in result.output +@dbtest +def test_multiple_execute_arg(executor): + run(executor, "create table test (a text)") + run(executor, 'insert into test values("abc")') + + sql_1 = "select * from test;" + sql_2 = "select 1234;" + runner = CliRunner() + result = runner.invoke(cli, args=CLI_ARGS + ["-e", sql_1, "-e", sql_2]) + + assert result.exit_code == 0 + assert "abc" in result.output + assert "1234" in result.output + + @dbtest def test_execute_arg_with_checkpoint(executor): run(executor, "create table test (a text)")