mycli 2.19.0__tar.gz → 2.20.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.19.0/mycli.egg-info → mycli-2.20.0}/PKG-INFO +8 -8
- {mycli-2.19.0 → mycli-2.20.0}/README.md +1 -1
- {mycli-2.19.0 → mycli-2.20.0}/changelog.md +30 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/client_commands.py +28 -141
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/completion_engine.py +59 -13
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/hybrid_redirection.py +31 -1
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/__init__.py +6 -3
- mycli-2.20.0/mycli/packages/special/source.py +169 -0
- {mycli-2.19.0 → mycli-2.20.0/mycli.egg-info}/PKG-INFO +8 -8
- {mycli-2.19.0 → mycli-2.20.0}/mycli.egg-info/SOURCES.txt +2 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli.egg-info/requires.txt +6 -6
- {mycli-2.19.0 → mycli-2.20.0}/mycli.egg-info/scm_file_list.json +2 -0
- mycli-2.20.0/mycli.egg-info/scm_version.json +8 -0
- {mycli-2.19.0 → mycli-2.20.0}/pyproject.toml +6 -6
- mycli-2.20.0/test/features/fixture_data/help_commands.txt +43 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_client_commands.py +91 -169
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_completion_engine.py +118 -9
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_hybrid_redirection.py +51 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_smart_completion_public_schema_only.py +50 -5
- mycli-2.20.0/test/pytests/test_special_source.py +187 -0
- mycli-2.19.0/mycli.egg-info/scm_version.json +0 -8
- mycli-2.19.0/test/features/fixture_data/help_commands.txt +0 -43
- {mycli-2.19.0 → mycli-2.20.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/dependabot.yml +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/workflows/ci.yml +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/workflows/publish.yml +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.github/workflows/typecheck.yml +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/.gitignore +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/AGENTS.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/AUTHORS.rst +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/CONTRIBUTING.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/LICENSE.txt +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/MANIFEST.in +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/SPONSORS.rst +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/doc/key_bindings.rst +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/doc/llm.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/doc/screenshots/total_histogram.png +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/doc/transforms.md +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/AUTHORS +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/SPONSORS +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/TIPS +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/app_state.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/boundary_tunnel.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/cli_runner.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/clibuffer.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/client.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/client_connection.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/client_query.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/clistyle.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/clitoolbar.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/compat.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/completion_refresher.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/config.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/constants.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/key_bindings.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/lexer.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main_modes/batch.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main_modes/completions.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main_modes/execute.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/main_modes/repl.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/myclirc +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/output.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/cli_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/filepaths.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/polars_completion.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/polars_transform.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/dsn_aliases.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/favoritequeries.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/iocommands.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/llm.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/main.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/special/utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/sqlresult.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/string_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/password_sources.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/resources/completions/bash/mycli +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/resources/completions/fish/mycli.fish +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/resources/completions/zsh/_mycli +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/sqlcompleter.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/sqlexecute.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/ssh_tunnel.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/types.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli/vault.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/setup.cfg +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/auto_vertical.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/basic_commands.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/connection.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/crud_database.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/crud_table.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/db_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/environment.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/fixture_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/iocommands.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/named_queries.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/specials.feature +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/__init__.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/basic_commands.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/connection.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/crud_database.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/crud_table.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/iocommands.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/named_queries.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/specials.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/steps/wrappers.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/features/wrappager.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/myclirc +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/mylogin.cnf +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/conftest.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_app_state.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_bash_completion.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_boundary_tunnel.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_checkup.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_cli_runner.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_cli_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_clibuffer.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_client.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_client_connection.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_client_query.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_config.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_dsn_aliases.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_filepaths.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_fish_completion.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_main.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_main_modes_repl.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_output.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_password_sources.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_polars_completion.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_polars_transform.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_special_init.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_special_iocommands.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_special_main.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_special_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_sqlexecute.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_sqlresult.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_ssh_tunnel.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_vault.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/pytests/test_zsh_completion.py +0 -0
- {mycli-2.19.0 → mycli-2.20.0}/test/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.20.0
|
|
4
4
|
Summary: Rich MySQL terminal client with autocompletion, syntax highlighting, and dataframes
|
|
5
5
|
Author: Mycli Core Team
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -30,7 +30,7 @@ License-File: LICENSE.txt
|
|
|
30
30
|
License-File: AUTHORS.rst
|
|
31
31
|
Requires-Dist: click~=8.5.0
|
|
32
32
|
Requires-Dist: clickdc~=0.1.1
|
|
33
|
-
Requires-Dist: cryptography~=50.0.
|
|
33
|
+
Requires-Dist: cryptography~=50.0.1
|
|
34
34
|
Requires-Dist: Pygments~=2.21.0
|
|
35
35
|
Requires-Dist: prompt_toolkit<4.0.0,>=3.0.41
|
|
36
36
|
Requires-Dist: PyMySQL~=1.1.2
|
|
@@ -39,11 +39,11 @@ Requires-Dist: sqlglot~=30.17.0
|
|
|
39
39
|
Requires-Dist: sqlglotc~=30.17.0
|
|
40
40
|
Requires-Dist: configobj~=5.0.9
|
|
41
41
|
Requires-Dist: cli_helpers[styles]~=2.15.1
|
|
42
|
-
Requires-Dist: wcwidth~=0.8.
|
|
42
|
+
Requires-Dist: wcwidth~=0.8.3
|
|
43
43
|
Requires-Dist: pyperclip~=1.11.0
|
|
44
44
|
Requires-Dist: pycryptodomex~=3.23.0
|
|
45
45
|
Requires-Dist: pyfzf~=0.3.1
|
|
46
|
-
Requires-Dist: rapidfuzz~=3.14.
|
|
46
|
+
Requires-Dist: rapidfuzz~=3.14.5
|
|
47
47
|
Requires-Dist: keyring~=25.7.0
|
|
48
48
|
Requires-Dist: jinja2~=3.1.6
|
|
49
49
|
Requires-Dist: yaspin~=3.4.0
|
|
@@ -59,21 +59,21 @@ Provides-Extra: all
|
|
|
59
59
|
Requires-Dist: mycli[dataframe,llm]; extra == "all"
|
|
60
60
|
Provides-Extra: dev
|
|
61
61
|
Requires-Dist: behave~=1.3.3; extra == "dev"
|
|
62
|
-
Requires-Dist: coverage~=7.
|
|
62
|
+
Requires-Dist: coverage~=7.15.4; extra == "dev"
|
|
63
63
|
Requires-Dist: mypy~=2.3.1; extra == "dev"
|
|
64
64
|
Requires-Dist: pexpect~=4.9.0; extra == "dev"
|
|
65
65
|
Requires-Dist: pytest~=9.1.1; extra == "dev"
|
|
66
|
-
Requires-Dist: pytest-cov~=7.
|
|
66
|
+
Requires-Dist: pytest-cov~=7.1.0; extra == "dev"
|
|
67
67
|
Requires-Dist: pytest-random-order~=1.2.0; extra == "dev"
|
|
68
68
|
Requires-Dist: tox~=4.35.0; extra == "dev"
|
|
69
69
|
Requires-Dist: pdbpp~=0.11.7; extra == "dev"
|
|
70
|
-
Requires-Dist: ruff~=0.16.
|
|
70
|
+
Requires-Dist: ruff~=0.16.5; extra == "dev"
|
|
71
71
|
Dynamic: license-file
|
|
72
72
|
|
|
73
73
|
# mycli
|
|
74
74
|
|
|
75
75
|
[](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli)
|
|
76
|
-
[](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
|
|
76
|
+
[](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
|
|
77
77
|
|
|
78
78
|
Rich MySQL terminal client with auto-completion, syntax highlighting, and dataframes.
|
|
79
79
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# mycli
|
|
2
2
|
|
|
3
3
|
[](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli)
|
|
4
|
-
[](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
|
|
4
|
+
[](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
|
|
5
5
|
|
|
6
6
|
Rich MySQL terminal client with auto-completion, syntax highlighting, and dataframes.
|
|
7
7
|
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
2.20.0 (2026/09/05)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Features
|
|
5
|
+
--------
|
|
6
|
+
* Add a `/source --throttle` option to pause between sourced statements.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Bug Fixes
|
|
10
|
+
--------
|
|
11
|
+
* Allow shell-style redirects with `/source` when the filename is unquoted.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Documentation
|
|
15
|
+
--------
|
|
16
|
+
* Badge color nit in `README.md`.
|
|
17
|
+
* Add a `/source --help` option.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Internal
|
|
21
|
+
---------
|
|
22
|
+
* Upgrade `cryptography` dependency to v50.0.1.
|
|
23
|
+
* Upgrade `rapidfuzz` dependency to v3.14.5.
|
|
24
|
+
* Upgrade `ruff` dev dependency to v0.16.5.
|
|
25
|
+
* Upgrade `coverage` dev dependency to v7.15.4.
|
|
26
|
+
* Upgrade `pytest-cov` dev dependency to v7.1.0.
|
|
27
|
+
* Upgrade `wcwidth` dependency to v0.8.3.
|
|
28
|
+
* Use `argparse` to process arguments to `/source`.
|
|
29
|
+
|
|
30
|
+
|
|
1
31
|
2.19.0 (2026/09/02)
|
|
2
32
|
==============
|
|
3
33
|
|
|
@@ -4,13 +4,11 @@ from collections.abc import Generator, Mapping
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
-
import
|
|
7
|
+
import time
|
|
8
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
-
import sqlparse
|
|
12
11
|
|
|
13
|
-
from mycli.compat import WIN
|
|
14
12
|
from mycli.config import write_default_config
|
|
15
13
|
from mycli.main_modes.repl import set_all_external_titles
|
|
16
14
|
from mycli.packages import special
|
|
@@ -18,9 +16,12 @@ from mycli.packages.batch_utils import statements_from_filehandle
|
|
|
18
16
|
from mycli.packages.filepaths import dir_path_exists
|
|
19
17
|
from mycli.packages.interactive_utils import confirm_destructive_query
|
|
20
18
|
from mycli.packages.ptoolkit.history import FileHistoryWithTimestamp
|
|
21
|
-
from mycli.packages.special import main as special_main
|
|
22
|
-
from mycli.packages.special.iocommands import expand_favorite_query
|
|
23
19
|
from mycli.packages.special.main import ArgType, SpecialCommandAlias
|
|
20
|
+
from mycli.packages.special.source import (
|
|
21
|
+
SOURCE_HELP_ROWS,
|
|
22
|
+
parse_source_arguments,
|
|
23
|
+
source_special_command_is_safe,
|
|
24
|
+
)
|
|
24
25
|
from mycli.packages.sqlresult import SQLResult
|
|
25
26
|
from mycli.sqlexecute import SQLExecute
|
|
26
27
|
|
|
@@ -36,30 +37,6 @@ MISSING_CONFIG_VALUE = object()
|
|
|
36
37
|
DSN_CONFIG_VALUE = object()
|
|
37
38
|
FAVORITES_CONFIG_VALUE = object()
|
|
38
39
|
HIDDEN_CONFIG_SECTIONS = frozenset({'alias_dsn', 'favorite_queries'})
|
|
39
|
-
INVALID_SOURCE_FILENAME = 'Source accepts exactly one filename; filenames containing spaces must be quoted.'
|
|
40
|
-
SOURCE_SAFE_SPECIAL_COMMANDS = frozenset({
|
|
41
|
-
'connect',
|
|
42
|
-
'fd',
|
|
43
|
-
'fs',
|
|
44
|
-
'help',
|
|
45
|
-
'l',
|
|
46
|
-
'nowarnings',
|
|
47
|
-
'ping',
|
|
48
|
-
'prompt',
|
|
49
|
-
'redirectformat',
|
|
50
|
-
'rehash',
|
|
51
|
-
'status',
|
|
52
|
-
'tableformat',
|
|
53
|
-
'timing',
|
|
54
|
-
'use',
|
|
55
|
-
'warnings',
|
|
56
|
-
'dt',
|
|
57
|
-
})
|
|
58
|
-
SOURCE_SAFE_SUBCOMMANDS = {
|
|
59
|
-
'config': frozenset({'help', 'get', 'search'}),
|
|
60
|
-
'dsn': frozenset({'help', 'list', 'show', 'save', 'delete'}),
|
|
61
|
-
'favorite': frozenset({'help', 'list', 'reload', 'run', 'save', 'delete'}),
|
|
62
|
-
}
|
|
63
40
|
|
|
64
41
|
|
|
65
42
|
def _render_config_value(value: Any) -> str:
|
|
@@ -68,106 +45,6 @@ def _render_config_value(value: Any) -> str:
|
|
|
68
45
|
return str(value)
|
|
69
46
|
|
|
70
47
|
|
|
71
|
-
def _parse_source_arguments(arg: str) -> tuple[str, bool, bool, bool]:
|
|
72
|
-
allow_special = False
|
|
73
|
-
show_queries = False
|
|
74
|
-
page_output = False
|
|
75
|
-
filename = arg
|
|
76
|
-
while arguments := filename.split(maxsplit=1):
|
|
77
|
-
if arguments[0] == '--special':
|
|
78
|
-
allow_special = True
|
|
79
|
-
elif arguments[0] == '--show':
|
|
80
|
-
show_queries = True
|
|
81
|
-
elif arguments[0] == '--page':
|
|
82
|
-
page_output = True
|
|
83
|
-
else:
|
|
84
|
-
break
|
|
85
|
-
filename = arguments[1] if len(arguments) == 2 else ''
|
|
86
|
-
return filename, allow_special, show_queries, page_output
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _has_unquoted_whitespace(value: str) -> bool:
|
|
90
|
-
quote: str | None = None
|
|
91
|
-
escaped = False
|
|
92
|
-
for character in value:
|
|
93
|
-
if escaped:
|
|
94
|
-
if quote is None and character.isspace():
|
|
95
|
-
return True
|
|
96
|
-
escaped = False
|
|
97
|
-
continue
|
|
98
|
-
if not WIN and character == '\\' and quote != "'":
|
|
99
|
-
escaped = True
|
|
100
|
-
continue
|
|
101
|
-
if character in ("'", '"'):
|
|
102
|
-
if quote is None:
|
|
103
|
-
quote = character
|
|
104
|
-
elif quote == character:
|
|
105
|
-
quote = None
|
|
106
|
-
elif quote is None and character.isspace():
|
|
107
|
-
return True
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _parse_source_filename(filename: str) -> str:
|
|
112
|
-
if not filename:
|
|
113
|
-
return ''
|
|
114
|
-
if _has_unquoted_whitespace(filename):
|
|
115
|
-
raise ValueError(INVALID_SOURCE_FILENAME)
|
|
116
|
-
try:
|
|
117
|
-
arguments = shlex.split(filename, posix=not WIN)
|
|
118
|
-
except ValueError as error:
|
|
119
|
-
raise ValueError(f'Invalid source filename: {error}.') from None
|
|
120
|
-
if len(arguments) != 1:
|
|
121
|
-
raise ValueError(INVALID_SOURCE_FILENAME)
|
|
122
|
-
parsed_filename = arguments[0]
|
|
123
|
-
if WIN and len(parsed_filename) >= 2 and parsed_filename[0] == parsed_filename[-1] and parsed_filename[0] in ("'", '"'):
|
|
124
|
-
parsed_filename = parsed_filename[1:-1]
|
|
125
|
-
return parsed_filename
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _registered_special_command(query: str) -> tuple[str, str] | None:
|
|
129
|
-
command, _verbosity, arg = special.parse_special_command(query)
|
|
130
|
-
registered = special_main.COMMANDS.get(command)
|
|
131
|
-
if registered is None:
|
|
132
|
-
registered = special_main.COMMANDS.get(command.lower())
|
|
133
|
-
if registered is None:
|
|
134
|
-
return None
|
|
135
|
-
return registered.command.removeprefix('\\').removeprefix('/').lower(), arg
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _favorite_source_command_is_safe(arg: str) -> bool:
|
|
139
|
-
query, _error = expand_favorite_query(arg)
|
|
140
|
-
if query is None:
|
|
141
|
-
return True
|
|
142
|
-
return not any(special.is_special_command(statement.rstrip(';')) for statement in sqlparse.split(query))
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _source_special_command_is_safe(query: str) -> bool:
|
|
146
|
-
parsed = _registered_special_command(query)
|
|
147
|
-
if parsed is None:
|
|
148
|
-
return False
|
|
149
|
-
|
|
150
|
-
command, arg = parsed
|
|
151
|
-
if command == 'f':
|
|
152
|
-
return not arg or _favorite_source_command_is_safe(arg)
|
|
153
|
-
if command in ('fd', 'fs'):
|
|
154
|
-
return True
|
|
155
|
-
if command in SOURCE_SAFE_SPECIAL_COMMANDS:
|
|
156
|
-
return True
|
|
157
|
-
|
|
158
|
-
subcommands = SOURCE_SAFE_SUBCOMMANDS.get(command)
|
|
159
|
-
if subcommands is None:
|
|
160
|
-
return False
|
|
161
|
-
arguments = arg.split(maxsplit=1)
|
|
162
|
-
subcommand = arguments[0].lower() if arguments else 'help'
|
|
163
|
-
if subcommand not in subcommands:
|
|
164
|
-
return False
|
|
165
|
-
if command == 'favorite' and subcommand == 'run':
|
|
166
|
-
run_arg = arguments[1] if len(arguments) == 2 else ''
|
|
167
|
-
return not run_arg or _favorite_source_command_is_safe(run_arg)
|
|
168
|
-
return True
|
|
169
|
-
|
|
170
|
-
|
|
171
48
|
def _iter_config_values(
|
|
172
49
|
config: Mapping[str, Any],
|
|
173
50
|
prefix: str = '',
|
|
@@ -279,7 +156,7 @@ class ClientCommandsMixin:
|
|
|
279
156
|
special.register_special_command(
|
|
280
157
|
self.execute_from_file,
|
|
281
158
|
"source",
|
|
282
|
-
"/source [
|
|
159
|
+
"/source [options] <file>",
|
|
283
160
|
"Execute queries from a file.",
|
|
284
161
|
aliases=[SpecialCommandAlias("\\.", case_sensitive=False)],
|
|
285
162
|
completion_snippet='execute queries from file',
|
|
@@ -412,16 +289,19 @@ class ClientCommandsMixin:
|
|
|
412
289
|
yield SQLResult(status=msg)
|
|
413
290
|
|
|
414
291
|
def execute_from_file(self, arg: str, **_) -> Generator[SQLResult, None, None]:
|
|
415
|
-
filename, allow_special, show_queries, page_output = _parse_source_arguments(arg)
|
|
416
|
-
if page_output:
|
|
417
|
-
yield SQLResult(command={'name': 'source_page'})
|
|
418
292
|
try:
|
|
419
|
-
|
|
293
|
+
source_arguments = parse_source_arguments(arg)
|
|
420
294
|
except ValueError as error:
|
|
421
295
|
yield SQLResult(status=str(error), is_error=True)
|
|
422
296
|
return
|
|
297
|
+
if source_arguments.show_help:
|
|
298
|
+
yield SQLResult(header=['Argument', 'Description'], rows=SOURCE_HELP_ROWS)
|
|
299
|
+
return
|
|
300
|
+
if source_arguments.page_output:
|
|
301
|
+
yield SQLResult(command={'name': 'source_page'})
|
|
302
|
+
filename = source_arguments.filename
|
|
423
303
|
if not filename:
|
|
424
|
-
yield SQLResult(status="Missing required argument: filename.", is_error=True)
|
|
304
|
+
yield SQLResult(status="Missing required argument: filename. See /source --help.", is_error=True)
|
|
425
305
|
return
|
|
426
306
|
|
|
427
307
|
try:
|
|
@@ -431,6 +311,7 @@ class ClientCommandsMixin:
|
|
|
431
311
|
return
|
|
432
312
|
|
|
433
313
|
assert isinstance(self.sqlexecute, SQLExecute)
|
|
314
|
+
executed_statement = False
|
|
434
315
|
with file_h:
|
|
435
316
|
statements = statements_from_filehandle(file_h)
|
|
436
317
|
while True:
|
|
@@ -444,35 +325,41 @@ class ClientCommandsMixin:
|
|
|
444
325
|
|
|
445
326
|
special_query = query.rstrip(';')
|
|
446
327
|
if special.is_special_command(special_query):
|
|
447
|
-
if not allow_special:
|
|
328
|
+
if not source_arguments.allow_special:
|
|
448
329
|
yield SQLResult(
|
|
449
330
|
status='Special commands are not supported without /source --special.',
|
|
450
331
|
is_error=True,
|
|
451
332
|
)
|
|
452
333
|
return
|
|
453
|
-
if not
|
|
334
|
+
if not source_special_command_is_safe(special_query):
|
|
454
335
|
command, _verbosity, _arg = special.parse_special_command(special_query)
|
|
455
336
|
yield SQLResult(
|
|
456
337
|
status=f'Special command is never permitted in source files: {command}.',
|
|
457
338
|
is_error=True,
|
|
458
339
|
)
|
|
459
340
|
return
|
|
460
|
-
if
|
|
461
|
-
|
|
341
|
+
if executed_statement and source_arguments.throttle > 0:
|
|
342
|
+
time.sleep(source_arguments.throttle)
|
|
343
|
+
if source_arguments.show_queries:
|
|
344
|
+
if source_arguments.page_output:
|
|
462
345
|
yield SQLResult(command={'name': 'source_show', 'text': special_query})
|
|
463
346
|
else:
|
|
464
347
|
click.secho(f'> {special_query}')
|
|
465
348
|
yield from self.sqlexecute.run(special_query)
|
|
349
|
+
executed_statement = True
|
|
466
350
|
continue
|
|
467
351
|
|
|
468
352
|
if self.destructive_warning and confirm_destructive_query(self.destructive_keywords, query) is False:
|
|
469
353
|
continue
|
|
470
|
-
if
|
|
471
|
-
|
|
354
|
+
if executed_statement and source_arguments.throttle > 0:
|
|
355
|
+
time.sleep(source_arguments.throttle)
|
|
356
|
+
if source_arguments.show_queries:
|
|
357
|
+
if source_arguments.page_output:
|
|
472
358
|
yield SQLResult(command={'name': 'source_show', 'text': query})
|
|
473
359
|
else:
|
|
474
360
|
click.secho(f'> {query}')
|
|
475
361
|
yield from self.sqlexecute.run(query)
|
|
362
|
+
executed_statement = True
|
|
476
363
|
|
|
477
364
|
def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
|
|
478
365
|
"""
|
|
@@ -11,6 +11,7 @@ from mycli.packages.special.dsn_aliases import DSN_SUBCOMMANDS
|
|
|
11
11
|
from mycli.packages.special.favoritequeries import FAVORITE_SUBCOMMANDS
|
|
12
12
|
from mycli.packages.special.main import COMMANDS as SPECIAL_COMMANDS
|
|
13
13
|
from mycli.packages.special.main import parse_special_command
|
|
14
|
+
from mycli.packages.special.source import SOURCE_BOOLEAN_OPTIONS, SOURCE_OPTIONS
|
|
14
15
|
from mycli.packages.sql_utils import extract_tables, find_prev_keyword, last_word
|
|
15
16
|
|
|
16
17
|
sqlparse.engine.grouping.MAX_GROUPING_DEPTH = None # type: ignore[assignment]
|
|
@@ -816,8 +817,24 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
|
|
|
816
817
|
'source',
|
|
817
818
|
'/source',
|
|
818
819
|
]:
|
|
819
|
-
source_options =
|
|
820
|
-
|
|
820
|
+
source_options = list(SOURCE_OPTIONS)
|
|
821
|
+
try:
|
|
822
|
+
source_arguments = shlex.split(_arg, posix=False)
|
|
823
|
+
except ValueError:
|
|
824
|
+
source_filename = _arg
|
|
825
|
+
while leading_arguments := source_filename.split(maxsplit=1):
|
|
826
|
+
if leading_arguments[0] in SOURCE_BOOLEAN_OPTIONS:
|
|
827
|
+
source_filename = leading_arguments[1] if len(leading_arguments) == 2 else ''
|
|
828
|
+
continue
|
|
829
|
+
if leading_arguments[0] == '--throttle' and len(leading_arguments) == 2:
|
|
830
|
+
throttle_arguments = leading_arguments[1].split(maxsplit=1)
|
|
831
|
+
source_filename = throttle_arguments[1] if len(throttle_arguments) == 2 else ''
|
|
832
|
+
continue
|
|
833
|
+
if leading_arguments[0].startswith('--throttle='):
|
|
834
|
+
source_filename = leading_arguments[1] if len(leading_arguments) == 2 else ''
|
|
835
|
+
continue
|
|
836
|
+
break
|
|
837
|
+
return [{'type': 'file_name', 'quote_spaces': True, 'source_filename': source_filename}]
|
|
821
838
|
if not source_arguments:
|
|
822
839
|
return [
|
|
823
840
|
{'type': 'special_subcommand', 'subcommands': source_options},
|
|
@@ -826,26 +843,55 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
|
|
|
826
843
|
|
|
827
844
|
used_options: set[str] = set()
|
|
828
845
|
argument_index = 0
|
|
829
|
-
|
|
830
|
-
|
|
846
|
+
filename_index: int | None = None
|
|
847
|
+
options_ended = False
|
|
848
|
+
while argument_index < len(source_arguments):
|
|
849
|
+
argument = source_arguments[argument_index]
|
|
850
|
+
if not options_ended and argument == '--':
|
|
851
|
+
options_ended = True
|
|
852
|
+
used_options.update(source_options)
|
|
853
|
+
argument_index += 1
|
|
854
|
+
continue
|
|
855
|
+
if not options_ended and argument in SOURCE_BOOLEAN_OPTIONS:
|
|
856
|
+
used_options.add(argument)
|
|
857
|
+
argument_index += 1
|
|
858
|
+
continue
|
|
859
|
+
if not options_ended and argument == '--help':
|
|
860
|
+
return []
|
|
861
|
+
if not options_ended and argument == '--throttle':
|
|
862
|
+
used_options.add(argument)
|
|
863
|
+
argument_index += 1
|
|
864
|
+
if argument_index >= len(source_arguments):
|
|
865
|
+
return []
|
|
866
|
+
if argument_index == len(source_arguments) - 1 and not text[-1].isspace():
|
|
867
|
+
return []
|
|
868
|
+
argument_index += 1
|
|
869
|
+
continue
|
|
870
|
+
if not options_ended and argument.startswith('--throttle='):
|
|
871
|
+
used_options.add('--throttle')
|
|
872
|
+
if argument == '--throttle=' or not text[-1].isspace():
|
|
873
|
+
return []
|
|
874
|
+
argument_index += 1
|
|
875
|
+
continue
|
|
876
|
+
if not options_ended and argument.startswith('-'):
|
|
877
|
+
break
|
|
878
|
+
if filename_index is not None:
|
|
879
|
+
return []
|
|
880
|
+
filename_index = argument_index
|
|
831
881
|
argument_index += 1
|
|
832
882
|
remaining_options = [option for option in source_options if option not in used_options]
|
|
833
|
-
source_filename =
|
|
834
|
-
for _index in range(argument_index):
|
|
835
|
-
parsed_argument = source_filename.split(maxsplit=1)
|
|
836
|
-
source_filename = parsed_argument[1] if len(parsed_argument) == 2 else ''
|
|
883
|
+
source_filename = source_arguments[filename_index] if filename_index is not None else ''
|
|
837
884
|
file_suggestion = {'type': 'file_name', 'quote_spaces': True, 'source_filename': source_filename}
|
|
838
885
|
|
|
839
886
|
if argument_index < len(source_arguments):
|
|
840
|
-
|
|
841
|
-
return [{'type': 'special_subcommand', 'subcommands': remaining_options}]
|
|
842
|
-
return [file_suggestion]
|
|
887
|
+
return [{'type': 'special_subcommand', 'subcommands': remaining_options}]
|
|
843
888
|
if not text[-1].isspace():
|
|
844
|
-
return []
|
|
889
|
+
return [file_suggestion] if filename_index == len(source_arguments) - 1 else []
|
|
845
890
|
suggestions: list[dict[str, Any]] = []
|
|
846
891
|
if remaining_options:
|
|
847
892
|
suggestions.append({'type': 'special_subcommand', 'subcommands': remaining_options})
|
|
848
|
-
|
|
893
|
+
if filename_index is None:
|
|
894
|
+
suggestions.append(file_suggestion)
|
|
849
895
|
return suggestions
|
|
850
896
|
|
|
851
897
|
if cmd.lower() in [
|
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import logging
|
|
3
|
+
import re
|
|
3
4
|
import shlex
|
|
4
5
|
|
|
5
6
|
import sqlglot
|
|
6
7
|
|
|
7
8
|
from mycli.compat import WIN
|
|
8
9
|
from mycli.packages.special.delimitercommand import DelimiterCommand
|
|
10
|
+
from mycli.packages.special.source import (
|
|
11
|
+
parse_source_arguments,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
logger = logging.getLogger(__name__)
|
|
11
15
|
delimiter_command = DelimiterCommand()
|
|
16
|
+
SOURCE_COMMAND_PATTERN = re.compile(r'^([/]?source|[/\\]\.)\s+', re.IGNORECASE)
|
|
17
|
+
SOURCE_OPTION_PATTERN = re.compile(r'(?<!\S)--(?:special|show|page|help|throttle)(?==|\s|$)', re.IGNORECASE)
|
|
18
|
+
SOURCE_OPTION_TERMINATOR_PATTERN = re.compile(r'(?<!\S)--(?=\s|$)')
|
|
19
|
+
HYBRID_OPERATOR_PATTERN = re.compile(r'\$(?:>>?|\|)')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def tokenize_command(command: str) -> list[sqlglot.Token]:
|
|
23
|
+
"""Tokenize a command without treating source options as SQL comments."""
|
|
24
|
+
if SOURCE_COMMAND_PATTERN.match(command):
|
|
25
|
+
operator_match = HYBRID_OPERATOR_PATTERN.search(command)
|
|
26
|
+
source_end = operator_match.start() if operator_match is not None else len(command)
|
|
27
|
+
source_part = command[:source_end]
|
|
28
|
+
terminator_match = SOURCE_OPTION_TERMINATOR_PATTERN.search(source_part)
|
|
29
|
+
options_end = terminator_match.start() if terminator_match is not None else len(source_part)
|
|
30
|
+
masked_options = SOURCE_OPTION_PATTERN.sub(lambda match: match.group().replace('-', '_'), source_part[:options_end])
|
|
31
|
+
command = masked_options + source_part[options_end:] + command[source_end:]
|
|
32
|
+
return sqlglot.tokenize(command)
|
|
12
33
|
|
|
13
34
|
|
|
14
35
|
def find_token_indices(tokens: list[sqlglot.Token]) -> dict[str, list[int]]:
|
|
@@ -44,6 +65,15 @@ def find_sql_part(
|
|
|
44
65
|
):
|
|
45
66
|
leftmost_dollar_pos = tokens[true_dollar_indices[0]].start
|
|
46
67
|
sql_part = command[0:leftmost_dollar_pos].strip().removesuffix(delimiter_command.current).rstrip()
|
|
68
|
+
if SOURCE_COMMAND_PATTERN.match(sql_part):
|
|
69
|
+
source_arg_str = SOURCE_COMMAND_PATTERN.sub('', sql_part)
|
|
70
|
+
try:
|
|
71
|
+
filename = parse_source_arguments(source_arg_str).filename
|
|
72
|
+
except ValueError:
|
|
73
|
+
return ''
|
|
74
|
+
if not filename:
|
|
75
|
+
return ''
|
|
76
|
+
return sql_part
|
|
47
77
|
try:
|
|
48
78
|
statements = sqlglot.parse(sql_part, read='mysql')
|
|
49
79
|
except sqlglot.errors.ParseError:
|
|
@@ -142,7 +172,7 @@ def get_redirect_components(command: str) -> tuple[str | None, str | None, str |
|
|
|
142
172
|
"""Get the parts of a hybrid shell-style redirect command."""
|
|
143
173
|
|
|
144
174
|
try:
|
|
145
|
-
tokens =
|
|
175
|
+
tokens = tokenize_command(command)
|
|
146
176
|
except sqlglot.errors.TokenError:
|
|
147
177
|
return None, None, None, None
|
|
148
178
|
|
|
@@ -3,6 +3,7 @@ __lazy_modules__ = [
|
|
|
3
3
|
'mycli.packages.special.iocommands',
|
|
4
4
|
'mycli.packages.special.llm',
|
|
5
5
|
'mycli.packages.special.main',
|
|
6
|
+
'mycli.packages.special.source',
|
|
6
7
|
]
|
|
7
8
|
|
|
8
9
|
import os
|
|
@@ -54,6 +55,7 @@ from mycli.packages.special.iocommands import (
|
|
|
54
55
|
write_pipe_once,
|
|
55
56
|
write_tee,
|
|
56
57
|
)
|
|
58
|
+
from mycli.packages.special.source import parse_source_arguments
|
|
57
59
|
|
|
58
60
|
if not os.environ.get('MYCLI_LLM_OFF'):
|
|
59
61
|
from mycli.packages.special.llm import (
|
|
@@ -112,12 +114,14 @@ __all__: list[str] = [
|
|
|
112
114
|
'is_llm_command',
|
|
113
115
|
'is_pager_enabled',
|
|
114
116
|
'is_redirected',
|
|
115
|
-
'
|
|
117
|
+
'is_show_favorite_query',
|
|
116
118
|
'is_show_warnings_enabled',
|
|
119
|
+
'is_special_command',
|
|
117
120
|
'is_timing_enabled',
|
|
118
121
|
'list_databases',
|
|
119
122
|
'list_tables',
|
|
120
123
|
'open_external_editor',
|
|
124
|
+
'parse_source_arguments',
|
|
121
125
|
'parse_special_command',
|
|
122
126
|
'ping',
|
|
123
127
|
'register_special_command',
|
|
@@ -131,10 +135,9 @@ __all__: list[str] = [
|
|
|
131
135
|
'set_pager',
|
|
132
136
|
'set_pager_enabled',
|
|
133
137
|
'set_redirect',
|
|
138
|
+
'set_show_favorite_query',
|
|
134
139
|
'set_show_warnings_enabled',
|
|
135
140
|
'set_timing_enabled',
|
|
136
|
-
'set_show_favorite_query',
|
|
137
|
-
'is_show_favorite_query',
|
|
138
141
|
'special_command',
|
|
139
142
|
'split_queries',
|
|
140
143
|
'sql_using_llm',
|