mycli 2.14.0__tar.gz → 2.16.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/ci.yml +5 -5
- {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/publish.yml +5 -5
- {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/typecheck.yml +1 -1
- {mycli-2.14.0/mycli.egg-info → mycli-2.16.0}/PKG-INFO +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/changelog.md +36 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/TIPS +8 -4
- {mycli-2.14.0 → mycli-2.16.0}/mycli/boundary_tunnel.py +4 -3
- {mycli-2.14.0 → mycli-2.16.0}/mycli/clibuffer.py +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/client.py +1 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/client_commands.py +167 -7
- {mycli-2.14.0 → mycli-2.16.0}/mycli/client_query.py +9 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/config.py +8 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main.py +0 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/execute.py +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/repl.py +163 -5
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/completion_engine.py +73 -4
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/filepaths.py +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/dsn_aliases.py +4 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/favoritequeries.py +122 -6
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/iocommands.py +185 -53
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/main.py +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/sqlresult.py +3 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/resources/completions/bash/mycli +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/resources/completions/fish/mycli.fish +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/resources/completions/zsh/_mycli +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/sqlcompleter.py +40 -1
- {mycli-2.14.0 → mycli-2.16.0}/mycli/sqlexecute.py +1 -1
- {mycli-2.14.0 → mycli-2.16.0/mycli.egg-info}/PKG-INFO +1 -1
- mycli-2.16.0/mycli.egg-info/scm_version.json +8 -0
- mycli-2.16.0/test/features/fixture_data/help_commands.txt +42 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_clibuffer.py +6 -3
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client_commands.py +355 -4
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client_query.py +60 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_completion_engine.py +108 -4
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_config.py +23 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_favoritequeries.py +211 -1
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_filepaths.py +1 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main.py +2 -2
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_execute.py +6 -6
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_repl.py +370 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_smart_completion_public_schema_only.py +80 -12
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_iocommands.py +438 -1
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_main.py +1 -1
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sqlexecute.py +10 -2
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sqlresult.py +2 -0
- mycli-2.14.0/mycli.egg-info/scm_version.json +0 -8
- mycli-2.14.0/test/features/fixture_data/help_commands.txt +0 -41
- {mycli-2.14.0 → mycli-2.16.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/.github/dependabot.yml +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/.gitignore +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/AGENTS.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/AUTHORS.rst +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/CONTRIBUTING.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/LICENSE.txt +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/MANIFEST.in +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/README.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/SPONSORS.rst +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/doc/key_bindings.rst +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/doc/llm.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/doc/screenshots/total_histogram.png +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/doc/transforms.md +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/AUTHORS +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/SPONSORS +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/app_state.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/cli_runner.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/client_connection.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/clistyle.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/clitoolbar.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/compat.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/completion_refresher.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/constants.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/key_bindings.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/lexer.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/batch.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/completions.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/myclirc +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/output.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/cli_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/hybrid_redirection.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/polars_transform.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/llm.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/string_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/password_sources.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/ssh_tunnel.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/types.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli/vault.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/SOURCES.txt +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/requires.txt +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/scm_file_list.json +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/pyproject.toml +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/setup.cfg +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/auto_vertical.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/basic_commands.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/connection.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/crud_database.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/crud_table.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/db_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/environment.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/fixture_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/iocommands.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/named_queries.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/specials.feature +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/__init__.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/basic_commands.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/connection.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/crud_database.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/crud_table.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/iocommands.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/named_queries.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/specials.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/wrappers.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/features/wrappager.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/myclirc +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/mylogin.cnf +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/conftest.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_app_state.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_bash_completion.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_boundary_tunnel.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_checkup.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_cli_runner.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_cli_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client_connection.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_dsn_aliases.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_fish_completion.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_lexer.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_output.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_password_sources.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_polars_transform.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_init.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ssh_tunnel.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_vault.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_zsh_completion.py +0 -0
- {mycli-2.14.0 → mycli-2.16.0}/test/utils.py +0 -0
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
steps:
|
|
25
25
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
26
|
|
|
27
|
-
- uses: astral-sh/setup-uv@
|
|
27
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
28
28
|
with:
|
|
29
29
|
version: "latest"
|
|
30
30
|
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
steps:
|
|
67
67
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
68
68
|
|
|
69
|
-
- uses: astral-sh/setup-uv@
|
|
69
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
70
70
|
with:
|
|
71
71
|
version: "latest"
|
|
72
72
|
|
|
@@ -116,7 +116,7 @@ jobs:
|
|
|
116
116
|
steps:
|
|
117
117
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
118
118
|
|
|
119
|
-
- uses: astral-sh/setup-uv@
|
|
119
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
120
120
|
with:
|
|
121
121
|
version: 'latest'
|
|
122
122
|
|
|
@@ -165,7 +165,7 @@ jobs:
|
|
|
165
165
|
run: |
|
|
166
166
|
mkdir -p /root/mycli
|
|
167
167
|
|
|
168
|
-
- uses: ubuntu/wsl/.github/actions/wsl-checkout@
|
|
168
|
+
- uses: ubuntu/wsl/.github/actions/wsl-checkout@e2114d0a5ebea62e6b2e1565f175871ed7558d20
|
|
169
169
|
with:
|
|
170
170
|
distro: Ubuntu-24.04
|
|
171
171
|
working-dir: /root/mycli
|
|
@@ -219,7 +219,7 @@ jobs:
|
|
|
219
219
|
steps:
|
|
220
220
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
221
221
|
|
|
222
|
-
- uses: astral-sh/setup-uv@
|
|
222
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
223
223
|
with:
|
|
224
224
|
version: "latest"
|
|
225
225
|
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
|
|
30
30
|
steps:
|
|
31
31
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
-
- uses: astral-sh/setup-uv@
|
|
32
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
33
33
|
with:
|
|
34
34
|
version: "latest"
|
|
35
35
|
|
|
@@ -74,7 +74,7 @@ jobs:
|
|
|
74
74
|
steps:
|
|
75
75
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
76
76
|
|
|
77
|
-
- uses: astral-sh/setup-uv@
|
|
77
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
78
78
|
with:
|
|
79
79
|
version: "latest"
|
|
80
80
|
|
|
@@ -124,7 +124,7 @@ jobs:
|
|
|
124
124
|
steps:
|
|
125
125
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
126
126
|
|
|
127
|
-
- uses: astral-sh/setup-uv@
|
|
127
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
128
128
|
with:
|
|
129
129
|
version: 'latest'
|
|
130
130
|
|
|
@@ -173,7 +173,7 @@ jobs:
|
|
|
173
173
|
run: |
|
|
174
174
|
mkdir -p /root/mycli
|
|
175
175
|
|
|
176
|
-
- uses: ubuntu/wsl/.github/actions/wsl-checkout@
|
|
176
|
+
- uses: ubuntu/wsl/.github/actions/wsl-checkout@e2114d0a5ebea62e6b2e1565f175871ed7558d20
|
|
177
177
|
with:
|
|
178
178
|
distro: Ubuntu-24.04
|
|
179
179
|
working-dir: /root/mycli
|
|
@@ -226,7 +226,7 @@ jobs:
|
|
|
226
226
|
|
|
227
227
|
steps:
|
|
228
228
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
229
|
-
- uses: astral-sh/setup-uv@
|
|
229
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
230
230
|
with:
|
|
231
231
|
version: "latest"
|
|
232
232
|
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
2.16.0 (2026/08/22)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Features
|
|
5
|
+
---------
|
|
6
|
+
* Advertise beta `--boundary-id` option in helpdoc and completions.
|
|
7
|
+
* Add `/source --special` to allow executing some special commands.
|
|
8
|
+
* Add `/source --show` to display each query before executing it.
|
|
9
|
+
* Add `/source --page` to display all output in one pager session.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Bug Fixes
|
|
13
|
+
---------
|
|
14
|
+
* Show CLI error on invalid `--execute` containing `/source`.
|
|
15
|
+
* Require `/source` filenames containing spaces to be quoted.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
2.15.0 (2026/08/20)
|
|
19
|
+
==============
|
|
20
|
+
|
|
21
|
+
Features
|
|
22
|
+
---------
|
|
23
|
+
* Add alternative interface `/favorite` for favorite queries.
|
|
24
|
+
* Add `/dsn edit` subcommand to edit an existing DSN.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Bug Fixes
|
|
28
|
+
---------
|
|
29
|
+
* Lower Boundary tunnel stabilization pause to 0.15 sec.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Documentation
|
|
33
|
+
---------
|
|
34
|
+
* TIPS: `/favorite run` composes with the `\G` and `\x` terminators.
|
|
35
|
+
|
|
36
|
+
|
|
1
37
|
2.14.0 (2026/08/15)
|
|
2
38
|
==============
|
|
3
39
|
|
|
@@ -54,7 +54,7 @@ copy the previous query to the clipboard with /clip!
|
|
|
54
54
|
|
|
55
55
|
edit a query in an external editor using <query>\edit!
|
|
56
56
|
|
|
57
|
-
edit a query in an external editor using /edit <
|
|
57
|
+
edit a query in an external editor using /edit <file>!
|
|
58
58
|
|
|
59
59
|
/f lists favorite queries; /f <name> executes a favorite!
|
|
60
60
|
|
|
@@ -64,9 +64,11 @@ edit a query in an external editor using /edit <filename>!
|
|
|
64
64
|
|
|
65
65
|
Favorite queries support Jinja templates and named arguments!
|
|
66
66
|
|
|
67
|
+
Manage favorite queries with /favorite!
|
|
68
|
+
|
|
67
69
|
/l lists databases!
|
|
68
70
|
|
|
69
|
-
/once <
|
|
71
|
+
/once <file> appends the next result to <file>!
|
|
70
72
|
|
|
71
73
|
/| <command> sends the next result to a subprocess!
|
|
72
74
|
|
|
@@ -146,6 +148,8 @@ display output vertically using \G at the end of a query!
|
|
|
146
148
|
|
|
147
149
|
display output in an interactive explorer \x at the end of a query!
|
|
148
150
|
|
|
151
|
+
"/favorite run" can be combined with the \G or \x terminators!
|
|
152
|
+
|
|
149
153
|
run SQL scripts in batch mode using the standard input!
|
|
150
154
|
|
|
151
155
|
indexed columns are tagged with a "*" in completion menus!
|
|
@@ -302,9 +306,9 @@ customize password sources/precedence with "password_sources" in ~/.myclirc!
|
|
|
302
306
|
|
|
303
307
|
redirect query output to a shell command with "$| <command>"!
|
|
304
308
|
|
|
305
|
-
redirect query output to a CSV file with "$> <
|
|
309
|
+
redirect query output to a CSV file with "$> <file>"!
|
|
306
310
|
|
|
307
|
-
append query output to a CSV file with "$>> <
|
|
311
|
+
append query output to a CSV file with "$>> <file>"!
|
|
308
312
|
|
|
309
313
|
run a command after shell redirects with "post_redirect_command" in ~/.myclirc!
|
|
310
314
|
|
|
@@ -15,7 +15,10 @@ from typing import IO
|
|
|
15
15
|
|
|
16
16
|
from mycli.compat import WIN
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# This should be as small as possible and yet still
|
|
19
|
+
# ward off SSL errors at connection time. We know
|
|
20
|
+
# that 0.1 is too small on at least one Mac.
|
|
21
|
+
TUNNEL_STABILIZATION_PAUSE = 0.15
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
class BoundaryTunnelError(RuntimeError):
|
|
@@ -148,8 +151,6 @@ class BoundaryTunnel:
|
|
|
148
151
|
self._raise_if_failed()
|
|
149
152
|
if self._is_listening():
|
|
150
153
|
self._ready.set()
|
|
151
|
-
# todo: if this works to ward off SSL errors at connection time,
|
|
152
|
-
# try making the pause as small as possible
|
|
153
154
|
time.sleep(TUNNEL_STABILIZATION_PAUSE)
|
|
154
155
|
return
|
|
155
156
|
time.sleep(0.05)
|
|
@@ -29,7 +29,7 @@ def _multiline_exception(text: str) -> bool:
|
|
|
29
29
|
# Multi-statement favorite query is a special case. Because there will
|
|
30
30
|
# be a semicolon separating statements, we can't consider semicolon an
|
|
31
31
|
# EOL. Let's consider an empty line an EOL instead.
|
|
32
|
-
if
|
|
32
|
+
if iocommands.is_favorite_save_command(text):
|
|
33
33
|
return orig.endswith("\n")
|
|
34
34
|
|
|
35
35
|
return (
|
|
@@ -122,6 +122,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
122
122
|
self.config,
|
|
123
123
|
myclirc,
|
|
124
124
|
c['main'].get('shared_favorites_file'),
|
|
125
|
+
system_config_files=self.system_config_files,
|
|
125
126
|
)
|
|
126
127
|
DsnAliases.instance = DsnAliases.from_config(
|
|
127
128
|
self.config,
|
|
@@ -4,16 +4,21 @@ from collections.abc import Generator, Mapping
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
+
import shlex
|
|
7
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
8
9
|
|
|
9
10
|
import click
|
|
11
|
+
import sqlparse
|
|
10
12
|
|
|
13
|
+
from mycli.compat import WIN
|
|
11
14
|
from mycli.config import write_default_config
|
|
12
15
|
from mycli.main_modes.repl import set_all_external_titles
|
|
13
16
|
from mycli.packages import special
|
|
14
17
|
from mycli.packages.batch_utils import statements_from_filehandle
|
|
15
18
|
from mycli.packages.filepaths import dir_path_exists
|
|
16
19
|
from mycli.packages.interactive_utils import confirm_destructive_query
|
|
20
|
+
from mycli.packages.special import main as special_main
|
|
21
|
+
from mycli.packages.special.iocommands import expand_favorite_query
|
|
17
22
|
from mycli.packages.special.main import ArgType, SpecialCommandAlias
|
|
18
23
|
from mycli.packages.sqlresult import SQLResult
|
|
19
24
|
from mycli.sqlexecute import SQLExecute
|
|
@@ -30,6 +35,29 @@ MISSING_CONFIG_VALUE = object()
|
|
|
30
35
|
DSN_CONFIG_VALUE = object()
|
|
31
36
|
FAVORITES_CONFIG_VALUE = object()
|
|
32
37
|
HIDDEN_CONFIG_SECTIONS = frozenset({'alias_dsn', 'favorite_queries'})
|
|
38
|
+
INVALID_SOURCE_FILENAME = 'Source accepts exactly one filename; filenames containing spaces must be quoted.'
|
|
39
|
+
SOURCE_SAFE_SPECIAL_COMMANDS = frozenset({
|
|
40
|
+
'connect',
|
|
41
|
+
'fd',
|
|
42
|
+
'fs',
|
|
43
|
+
'help',
|
|
44
|
+
'l',
|
|
45
|
+
'nowarnings',
|
|
46
|
+
'prompt',
|
|
47
|
+
'redirectformat',
|
|
48
|
+
'rehash',
|
|
49
|
+
'status',
|
|
50
|
+
'tableformat',
|
|
51
|
+
'timing',
|
|
52
|
+
'use',
|
|
53
|
+
'warnings',
|
|
54
|
+
'dt',
|
|
55
|
+
})
|
|
56
|
+
SOURCE_SAFE_SUBCOMMANDS = {
|
|
57
|
+
'config': frozenset({'help', 'get', 'search'}),
|
|
58
|
+
'dsn': frozenset({'help', 'list', 'show', 'save', 'delete'}),
|
|
59
|
+
'favorite': frozenset({'help', 'list', 'reload', 'run', 'save', 'delete'}),
|
|
60
|
+
}
|
|
33
61
|
|
|
34
62
|
|
|
35
63
|
def _render_config_value(value: Any) -> str:
|
|
@@ -38,6 +66,106 @@ def _render_config_value(value: Any) -> str:
|
|
|
38
66
|
return str(value)
|
|
39
67
|
|
|
40
68
|
|
|
69
|
+
def _parse_source_arguments(arg: str) -> tuple[str, bool, bool, bool]:
|
|
70
|
+
allow_special = False
|
|
71
|
+
show_queries = False
|
|
72
|
+
page_output = False
|
|
73
|
+
filename = arg
|
|
74
|
+
while arguments := filename.split(maxsplit=1):
|
|
75
|
+
if arguments[0] == '--special':
|
|
76
|
+
allow_special = True
|
|
77
|
+
elif arguments[0] == '--show':
|
|
78
|
+
show_queries = True
|
|
79
|
+
elif arguments[0] == '--page':
|
|
80
|
+
page_output = True
|
|
81
|
+
else:
|
|
82
|
+
break
|
|
83
|
+
filename = arguments[1] if len(arguments) == 2 else ''
|
|
84
|
+
return filename, allow_special, show_queries, page_output
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _has_unquoted_whitespace(value: str) -> bool:
|
|
88
|
+
quote: str | None = None
|
|
89
|
+
escaped = False
|
|
90
|
+
for character in value:
|
|
91
|
+
if escaped:
|
|
92
|
+
if quote is None and character.isspace():
|
|
93
|
+
return True
|
|
94
|
+
escaped = False
|
|
95
|
+
continue
|
|
96
|
+
if not WIN and character == '\\' and quote != "'":
|
|
97
|
+
escaped = True
|
|
98
|
+
continue
|
|
99
|
+
if character in ("'", '"'):
|
|
100
|
+
if quote is None:
|
|
101
|
+
quote = character
|
|
102
|
+
elif quote == character:
|
|
103
|
+
quote = None
|
|
104
|
+
elif quote is None and character.isspace():
|
|
105
|
+
return True
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _parse_source_filename(filename: str) -> str:
|
|
110
|
+
if not filename:
|
|
111
|
+
return ''
|
|
112
|
+
if _has_unquoted_whitespace(filename):
|
|
113
|
+
raise ValueError(INVALID_SOURCE_FILENAME)
|
|
114
|
+
try:
|
|
115
|
+
arguments = shlex.split(filename, posix=not WIN)
|
|
116
|
+
except ValueError as error:
|
|
117
|
+
raise ValueError(f'Invalid source filename: {error}.') from None
|
|
118
|
+
if len(arguments) != 1:
|
|
119
|
+
raise ValueError(INVALID_SOURCE_FILENAME)
|
|
120
|
+
parsed_filename = arguments[0]
|
|
121
|
+
if WIN and len(parsed_filename) >= 2 and parsed_filename[0] == parsed_filename[-1] and parsed_filename[0] in ("'", '"'):
|
|
122
|
+
parsed_filename = parsed_filename[1:-1]
|
|
123
|
+
return parsed_filename
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _registered_special_command(query: str) -> tuple[str, str] | None:
|
|
127
|
+
command, _verbosity, arg = special.parse_special_command(query)
|
|
128
|
+
registered = special_main.COMMANDS.get(command)
|
|
129
|
+
if registered is None:
|
|
130
|
+
registered = special_main.COMMANDS.get(command.lower())
|
|
131
|
+
if registered is None:
|
|
132
|
+
return None
|
|
133
|
+
return registered.command.removeprefix('\\').removeprefix('/').lower(), arg
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _favorite_source_command_is_safe(arg: str) -> bool:
|
|
137
|
+
query, _error = expand_favorite_query(arg)
|
|
138
|
+
if query is None:
|
|
139
|
+
return True
|
|
140
|
+
return not any(special.is_special_command(statement.rstrip(';')) for statement in sqlparse.split(query))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _source_special_command_is_safe(query: str) -> bool:
|
|
144
|
+
parsed = _registered_special_command(query)
|
|
145
|
+
if parsed is None:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
command, arg = parsed
|
|
149
|
+
if command == 'f':
|
|
150
|
+
return not arg or _favorite_source_command_is_safe(arg)
|
|
151
|
+
if command in ('fd', 'fs'):
|
|
152
|
+
return True
|
|
153
|
+
if command in SOURCE_SAFE_SPECIAL_COMMANDS:
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
subcommands = SOURCE_SAFE_SUBCOMMANDS.get(command)
|
|
157
|
+
if subcommands is None:
|
|
158
|
+
return False
|
|
159
|
+
arguments = arg.split(maxsplit=1)
|
|
160
|
+
subcommand = arguments[0].lower() if arguments else 'help'
|
|
161
|
+
if subcommand not in subcommands:
|
|
162
|
+
return False
|
|
163
|
+
if command == 'favorite' and subcommand == 'run':
|
|
164
|
+
run_arg = arguments[1] if len(arguments) == 2 else ''
|
|
165
|
+
return not run_arg or _favorite_source_command_is_safe(run_arg)
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
|
|
41
169
|
def _iter_config_values(
|
|
42
170
|
config: Mapping[str, Any],
|
|
43
171
|
prefix: str = '',
|
|
@@ -143,7 +271,7 @@ class ClientCommandsMixin:
|
|
|
143
271
|
special.register_special_command(
|
|
144
272
|
self.execute_from_file,
|
|
145
273
|
"source",
|
|
146
|
-
"/source <
|
|
274
|
+
"/source [--special|--show|--page] <file>",
|
|
147
275
|
"Execute queries from a file.",
|
|
148
276
|
aliases=[SpecialCommandAlias("\\.", case_sensitive=False)],
|
|
149
277
|
)
|
|
@@ -266,12 +394,20 @@ class ClientCommandsMixin:
|
|
|
266
394
|
yield SQLResult(status=msg)
|
|
267
395
|
|
|
268
396
|
def execute_from_file(self, arg: str, **_) -> Generator[SQLResult, None, None]:
|
|
269
|
-
|
|
270
|
-
|
|
397
|
+
filename, allow_special, show_queries, page_output = _parse_source_arguments(arg)
|
|
398
|
+
if page_output:
|
|
399
|
+
yield SQLResult(command={'name': 'source_page'})
|
|
400
|
+
try:
|
|
401
|
+
filename = _parse_source_filename(filename)
|
|
402
|
+
except ValueError as error:
|
|
403
|
+
yield SQLResult(status=str(error), is_error=True)
|
|
404
|
+
return
|
|
405
|
+
if not filename:
|
|
406
|
+
yield SQLResult(status="Missing required argument: filename.", is_error=True)
|
|
271
407
|
return
|
|
272
408
|
|
|
273
409
|
try:
|
|
274
|
-
file_h = open(os.path.expanduser(
|
|
410
|
+
file_h = open(os.path.expanduser(filename))
|
|
275
411
|
except OSError as error:
|
|
276
412
|
yield SQLResult(status=str(error))
|
|
277
413
|
return
|
|
@@ -288,12 +424,36 @@ class ClientCommandsMixin:
|
|
|
288
424
|
yield SQLResult(status=str(error))
|
|
289
425
|
return
|
|
290
426
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
427
|
+
special_query = query.rstrip(';')
|
|
428
|
+
if special.is_special_command(special_query):
|
|
429
|
+
if not allow_special:
|
|
430
|
+
yield SQLResult(
|
|
431
|
+
status='Special commands are not supported without /source --special.',
|
|
432
|
+
is_error=True,
|
|
433
|
+
)
|
|
434
|
+
return
|
|
435
|
+
if not _source_special_command_is_safe(special_query):
|
|
436
|
+
command, _verbosity, _arg = special.parse_special_command(special_query)
|
|
437
|
+
yield SQLResult(
|
|
438
|
+
status=f'Special command is never permitted in source files: {command}.',
|
|
439
|
+
is_error=True,
|
|
440
|
+
)
|
|
441
|
+
return
|
|
442
|
+
if show_queries:
|
|
443
|
+
if page_output:
|
|
444
|
+
yield SQLResult(command={'name': 'source_show', 'text': special_query})
|
|
445
|
+
else:
|
|
446
|
+
click.secho(f'> {special_query}')
|
|
447
|
+
yield from self.sqlexecute.run(special_query)
|
|
448
|
+
continue
|
|
294
449
|
|
|
295
450
|
if self.destructive_warning and confirm_destructive_query(self.destructive_keywords, query) is False:
|
|
296
451
|
continue
|
|
452
|
+
if show_queries:
|
|
453
|
+
if page_output:
|
|
454
|
+
yield SQLResult(command={'name': 'source_show', 'text': query})
|
|
455
|
+
else:
|
|
456
|
+
click.secho(f'> {query}')
|
|
297
457
|
yield from self.sqlexecute.run(query)
|
|
298
458
|
|
|
299
459
|
def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
|
|
@@ -10,6 +10,10 @@ from mycli.packages.sqlresult import SQLResult
|
|
|
10
10
|
from mycli.sqlcompleter import SQLCompleter
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
class QueryError(Exception):
|
|
14
|
+
"""An error reported while consuming query results."""
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
class ClientQueryMixin:
|
|
14
18
|
if TYPE_CHECKING:
|
|
15
19
|
schema_prefetcher: Any
|
|
@@ -80,6 +84,7 @@ class ClientQueryMixin:
|
|
|
80
84
|
query: str,
|
|
81
85
|
checkpoint: str | None = None,
|
|
82
86
|
new_line: bool = True,
|
|
87
|
+
raise_on_error: bool = False,
|
|
83
88
|
) -> None:
|
|
84
89
|
"""Runs *query*."""
|
|
85
90
|
assert self.sqlexecute is not None
|
|
@@ -91,6 +96,10 @@ class ClientQueryMixin:
|
|
|
91
96
|
self.main_formatter.query = query
|
|
92
97
|
self.redirect_formatter.query = query
|
|
93
98
|
self.explorer_formatter.query = query
|
|
99
|
+
if result.is_error and raise_on_error:
|
|
100
|
+
message = result.status_plain or 'Query failed.'
|
|
101
|
+
self.log_output(message)
|
|
102
|
+
raise QueryError(message)
|
|
94
103
|
output = self.format_sqlresult(
|
|
95
104
|
result,
|
|
96
105
|
is_expanded=special.is_expanded_output(),
|
|
@@ -48,6 +48,7 @@ def read_config_file(
|
|
|
48
48
|
f: str | IO[str],
|
|
49
49
|
list_values: bool = True,
|
|
50
50
|
preserve_quotes: bool = False,
|
|
51
|
+
raise_errors: bool = False,
|
|
51
52
|
) -> ConfigObj | LimiitedQuotePreservingConfigObj | None:
|
|
52
53
|
"""Read a config file.
|
|
53
54
|
|
|
@@ -57,6 +58,9 @@ def read_config_file(
|
|
|
57
58
|
not unquoted. We are disabling list_values when reading MySQL config files
|
|
58
59
|
so we can correctly interpret commas in passwords.
|
|
59
60
|
|
|
61
|
+
Set *raise_errors* to propagate parsing and I/O errors instead of logging
|
|
62
|
+
them and returning a partial config or ``None``.
|
|
63
|
+
|
|
60
64
|
"""
|
|
61
65
|
|
|
62
66
|
if isinstance(f, str):
|
|
@@ -68,10 +72,14 @@ def read_config_file(
|
|
|
68
72
|
else:
|
|
69
73
|
config = ConfigObj(f, interpolation=False, encoding="utf8", list_values=list_values)
|
|
70
74
|
except ConfigObjError as e:
|
|
75
|
+
if raise_errors:
|
|
76
|
+
raise
|
|
71
77
|
log(logger, logging.WARNING, "Unable to parse line {0} of config file '{1}'.".format(e.line_number, f))
|
|
72
78
|
log(logger, logging.WARNING, "Using successfully parsed config values.")
|
|
73
79
|
return e.config
|
|
74
80
|
except (IOError, OSError) as e:
|
|
81
|
+
if raise_errors:
|
|
82
|
+
raise
|
|
75
83
|
log(logger, logging.WARNING, "You don't have permission to read config file '{0}'.".format(e.filename))
|
|
76
84
|
return None
|
|
77
85
|
|
|
@@ -46,7 +46,7 @@ def main_execute_from_cli(mycli: 'MyCli', cli_args: 'CliArgs') -> int:
|
|
|
46
46
|
mycli.logger.warning('Unable to open TTY as stdin.')
|
|
47
47
|
raise e
|
|
48
48
|
if execution_confirmed:
|
|
49
|
-
mycli.run_query(execute_sql, checkpoint=cli_args.checkpoint)
|
|
49
|
+
mycli.run_query(execute_sql, checkpoint=cli_args.checkpoint, raise_on_error=True)
|
|
50
50
|
return 0
|
|
51
51
|
else:
|
|
52
52
|
return 1
|