diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-15 06:48:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-15 06:48:23 +0000 |
commit | e24e64129a48154a492aa20e4ab91718ff4e4650 (patch) | |
tree | b37c136fe4d20df22c878121cbe9501a1239aba4 | |
parent | Adding upstream version 1.27.2. (diff) | |
download | mycli-upstream.tar.xz mycli-upstream.zip |
Adding upstream version 1.28.0.upstream/1.28.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | changelog.md | 348 | ||||
-rw-r--r-- | mycli/AUTHORS | 2 | ||||
-rw-r--r-- | mycli/__init__.py | 2 | ||||
-rw-r--r-- | mycli/key_bindings.py | 8 | ||||
-rwxr-xr-x | mycli/main.py | 4 | ||||
-rw-r--r-- | mycli/packages/completion_engine.py | 2 | ||||
-rw-r--r-- | mycli/packages/filepaths.py | 2 | ||||
-rw-r--r-- | mycli/packages/toolkit/__init__.py | 0 | ||||
-rw-r--r-- | mycli/packages/toolkit/fzf.py | 45 | ||||
-rw-r--r-- | mycli/packages/toolkit/history.py | 52 | ||||
-rw-r--r-- | mycli/sqlcompleter.py | 8 | ||||
-rw-r--r-- | requirements-dev.txt | 2 | ||||
-rwxr-xr-x | setup.py | 3 | ||||
-rw-r--r-- | test/myclirc | 3 | ||||
-rw-r--r-- | test/test_smart_completion_public_schema_only.py | 530 |
16 files changed, 610 insertions, 403 deletions
@@ -181,7 +181,7 @@ $ sudo dnf install mycli ### Windows -Follow the instructions on this blogpost: https://www.codewall.co.uk/installing-using-mycli-on-windows/ +Follow the instructions on this blogpost: http://web.archive.org/web/20221006045208/https://www.codewall.co.uk/installing-using-mycli-on-windows/ ### Thanks: diff --git a/changelog.md b/changelog.md index fb32e5a..6cab6f5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,31 +1,30 @@ -Upcoming Release (TBD) +1.28.0 (2024/11/10) ====================== -Bug Fixes: ----------- - - -Internal: +Features --------- -Features: ---------- +* Added fzf history search functionality. The feature can switch between the old implementation and the new one based on the presence of the fzf binary. +Bug Fixes +---------- + +* Fixes `Database connection failed: error('unpack requires a buffer of 4 bytes')` +* Only show keyword completions after * +* Enable fuzzy matching for keywords 1.27.2 (2024/04/03) =================== -Bug Fixes: +Bug Fixes ---------- * Don't use default prompt when one is not supplied to the --prompt option. - 1.27.1 (2024/03/28) =================== - -Bug Fixes: +Bug Fixes ---------- * Don't install tests. @@ -33,24 +32,22 @@ Bug Fixes: * Fix unexpected exception when using dsn without username & password (Thanks: [Will Wang]) * Let the `--prompt` option act normally with its predefined default value - - -Internal: +Internal --------- + * paramiko is newer than 2.11.0 now, remove version pinning `cryptography`. * Drop support for Python 3.7 - 1.27.0 (2023/08/11) =================== -Features: +Features --------- * Detect TiDB instance, show in the prompt, and use additional keywords. * Fix the completion order to show more commonly-used keywords at the top. -Bug Fixes: +Bug Fixes ---------- * Better handle empty statements in un/prettify @@ -59,139 +56,146 @@ Bug Fixes: * Correctly report the version of TiDB. * Revised `botton` spelling mistakes with `bottom` in `mycli/clitoolbar.py` - 1.26.1 (2022/09/01) =================== -Bug Fixes: +Bug Fixes ---------- -* Require Python 3.7 in `setup.py` +* Require Python 3.7 in `setup.py` 1.26.0 (2022/09/01) =================== -Features: +Features --------- * Add `--ssl` flag to enable ssl/tls. * Add `pager` option to `~/.myclirc`, for instance `pager = 'pspg --csv'` (Thanks: [BuonOmo]) * Add prettify/unprettify keybindings to format the current statement using `sqlglot`. - -Features: +Features --------- + * Add `--tls-version` option to control the tls version used. -Internal: +Internal --------- + * Pin `cryptography` to suppress `paramiko` warning, helping CI complete and presumably affecting some users. * Upgrade some dev requirements * Change tests to always use databases prefixed with 'mycli_' for better security -Bug Fixes: +Bug Fixes ---------- + * Support for some MySQL compatible databases, which may not implement connection_id(). * Fix the status command to work with missing 'Flush_commands' (mariadb) * Ignore the user of the system [myslqd] config. - 1.25.0 (2022/04/02) =================== -Features: +Features --------- -* Add `beep_after_seconds` option to `~/.myclirc`, to ring the terminal bell after long queries. +* Add `beep_after_seconds` option to `~/.myclirc`, to ring the terminal bell after long queries. 1.24.4 (2022/03/30) =================== -Internal: +Internal --------- + * Upgrade Ubuntu VM for runners as Github has deprecated it -Bug Fixes: +Bug Fixes ---------- -* Change in main.py - Replace the `click.get_terminal_size()` with `shutil.get_terminal_size()` - +* Change in main.py - Replace the `click.get_terminal_size()` with `shutil.get_terminal_size()` 1.24.3 (2022/01/20) =================== -Bug Fixes: +Bug Fixes ---------- -* Upgrade cli_helpers to workaround Pygments regression. +* Upgrade cli_helpers to workaround Pygments regression. 1.24.2 (2022/01/11) =================== -Bug Fixes: +Bug Fixes ---------- + * Fix autocompletion for more than one JOIN * Fix the status command when connected to TiDB or other servers that don't implement 'Threads\_connected' * Pin pygments version to avoid a breaking change -1.24.1: +1.24.1 ======= -Bug Fixes: +Bug Fixes --------- + * Restore dependency on cryptography for the interactive password prompt -Internal: +Internal --------- -* Deprecate Python mock +* Deprecate Python mock 1.24.0 ====== -Bug Fixes: +Bug Fixes ---------- + * Allow `FileNotFound` exception for SSH config files. * Fix startup error on MySQL < 5.0.22 * Check error code rather than message for Access Denied error * Fix login with ~/.my.cnf files -Features: +Features --------- + * Add `-g` shortcut to option `--login-path`. * Alt-Enter dispatches the command in multi-line mode. -* Allow to pass a file or FIFO path with --password-file when password is not specified or is failing (as suggested in this best-practice https://www.netmeister.org/blog/passing-passwords.html) +* Allow to pass a file or FIFO path with --password-file when password is not specified or is failing (as suggested in this best-practice <https://www.netmeister.org/blog/passing-passwords.html>) -Internal: +Internal --------- + * Remove unused function is_open_quote() * Use importlib, instead of file links, to locate resources * Test various host-port combinations in command line arguments * Switched from Cryptography to pyaes for decrypting mylogin.cnf - 1.23.2 ====== -Bug Fixes: +Bug Fixes ---------- + * Ensure `--port` is always an int. 1.23.1 ====== -Bug Fixes: +Bug Fixes ---------- + * Allow `--host` without `--port` to make a TCP connection. 1.23.0 ====== -Bug Fixes: +Bug Fixes ---------- + * Fix config file include logic -Features: +Features --------- * Add an option `--init-command` to execute SQL after connecting (Thanks: [KITAGAWA Yasutaka]). @@ -202,10 +206,11 @@ Features: * Add a special command `\pipe_once` to pipe output to a subprocess. * Add an option `--charset` to set the default charset when connect database. -Bug Fixes: +Bug Fixes ---------- + * Fixed compatibility with sqlparse 0.4 (Thanks: [mtorromeo]). -* Fixed iPython magic (Thanks: [mwcm]). +* Fixed iPython magic (Thanks: [mwcm]). * Send "Connecting to socket" message to the standard error. * Respect empty string for prompt_continuation via `prompt_continuation = ''` in `.myclirc` * Fix \once -o to overwrite output whole, instead of line-by-line. @@ -218,35 +223,35 @@ Bug Fixes: 1.22.2 ====== -Bug Fixes: +Bug Fixes ---------- -* Make the `pwd` module optional. +* Make the `pwd` module optional. 1.22.1 ====== -Bug Fixes: +Bug Fixes ---------- + * Fix the breaking change introduced in PyMySQL 0.10.0. (Thanks: [Amjith]). -Features: +Features --------- + * Add an option `--ssh-config-host` to read ssh configuration from OpenSSH configuration file. * Add an option `--list-ssh-config` to list ssh configurations. * Add an option `--ssh-config-path` to choose ssh configuration path. -Bug Fixes: +Bug Fixes ---------- * Fix specifying empty password with `--password=''` when config file has a password set (Thanks: [Zach DeCook]). - 1.21.1 ====== - -Bug Fixes: +Bug Fixes ---------- * Fix broken auto-completion for favorite queries (Thanks: [Amjith]). @@ -256,8 +261,9 @@ Bug Fixes: 1.21.0 ====== -Features: +Features --------- + * Added DSN alias name as a format specifier to the prompt (Thanks: [Georgy Frolov]). * Mark `update` without `where`-clause as destructive query (Thanks: [Klaus Wünschel]). * Added DELIMITER command (Thanks: [Georgy Frolov]) @@ -265,20 +271,21 @@ Features: * Extend main.is_dropping_database check with create after delete statement. * Search `${XDG_CONFIG_HOME}/mycli/myclirc` after `${HOME}/.myclirc` and before `/etc/myclirc` (Thanks: [Takeshi D. Itoh]) -Bug Fixes: +Bug Fixes ---------- * Allow \o command more than once per session (Thanks: [Georgy Frolov]) * Fixed crash when the query dropping the current database starts with a comment (Thanks: [Georgy Frolov]) -Internal: +Internal --------- + * deprecate python versions 2.7, 3.4, 3.5; support python 3.8 1.20.1 ====== -Bug Fixes: +Bug Fixes ---------- * Fix an error when using login paths with an explicit database name (Thanks: [Thomas Roten]). @@ -286,14 +293,15 @@ Bug Fixes: 1.20.0 ====== -Features: +Features ---------- + * Auto find alias dsn when `://` not in `database` (Thanks: [QiaoHou Peng]). * Mention URL encoding as escaping technique for special characters in connection DSN (Thanks: [Aljosha Papsch]). * Pressing Alt-Enter will introduce a line break. This is a way to break up the query into multiple lines without switching to multi-line mode. (Thanks: [Amjith Ramanujam]). * Use a generator to stream the output to the pager (Thanks: [Dick Marinus]). -Bug Fixes: +Bug Fixes ---------- * Fix the missing completion for special commands (Thanks: [Amjith Ramanujam]). @@ -303,28 +311,29 @@ Bug Fixes: * Update `setup.py` to no longer require `sqlparse` to be less than 0.3.0 as that just came out and there are no notable changes. ([VVelox]) * workaround for ConfigObj parsing strings containing "," as lists (Thanks: [Mike Palandra]) -Internal: +Internal --------- + * fix unhashable FormattedText from prompt toolkit in unit tests (Thanks: [Dick Marinus]). 1.19.0 ====== -Internal: +Internal --------- * Add Python 3.7 trove classifier (Thanks: [Thomas Roten]). * Fix pytest in Fedora mock (Thanks: [Dick Marinus]). * Require `prompt_toolkit>=2.0.6` (Thanks: [Dick Marinus]). -Features: +Features --------- * Add Token.Prompt/Continuation (Thanks: [Dick Marinus]). * Don't reconnect when switching databases using use (Thanks: [Angelo Lupo]). * Handle MemoryErrors while trying to pipe in large files and exit gracefully with an error (Thanks: [Amjith Ramanujam]) -Bug Fixes: +Bug Fixes ---------- * Enable Ctrl-Z to suspend the app (Thanks: [Amjith Ramanujam]). @@ -332,12 +341,12 @@ Bug Fixes: 1.18.2 ====== -Bug Fixes: +Bug Fixes ---------- * Fixes database reconnecting feature (Thanks: [Yang Zou]). -Internal: +Internal --------- * Update Twine version to 1.12.1 (Thanks: [Thomas Roten]). @@ -347,12 +356,12 @@ Internal: 1.18.1 ====== -Features: +Features --------- * Add Keywords: TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT (Thanks: [QiaoHou Peng]). -Internal: +Internal --------- * Update prompt toolkit (Thanks: [Jonathan Slenders], [Irina Truong], [Dick Marinus]). @@ -360,7 +369,7 @@ Internal: 1.18.0 ====== -Features: +Features --------- * Display server version in welcome message (Thanks: [Irina Truong]). @@ -371,30 +380,30 @@ Features: * Add `FROM_UNIXTIME` and `UNIX_TIMESTAMP` to SQLCompleter (Thanks: [QiaoHou Peng]) * Search `${PWD}/.myclirc`, then `${HOME}/.myclirc`, lastly `/etc/myclirc` (Thanks: [QiaoHao Peng]) -Bug Fixes: +Bug Fixes ---------- * When DSN is used, allow overrides from mycli arguments (Thanks: [Dick Marinus]). * A DSN without password should be allowed (Thanks: [Dick Marinus]) -Bug Fixes: +Bug Fixes ---------- * Convert `sql_format` to unicode strings for py27 compatibility (Thanks: [Dick Marinus]). * Fixes mycli compatibility with pbr (Thanks: [Thomas Roten]). * Don't align decimals for `sql_format` (Thanks: [Dick Marinus]). -Internal: +Internal --------- * Use fileinput (Thanks: [Dick Marinus]). * Enable tests for Python 3.7 (Thanks: [Thomas Roten]). * Remove `*.swp` from gitignore (Thanks: [Dick Marinus]). -1.17.0: +1.17.0 ======= -Features: +Features ---------- * Add `CONCAT` to SQLCompleter and remove unused code (Thanks: [caitinggui]) @@ -402,7 +411,7 @@ Features: * Add option list-dsn (Thanks: [Frederic Aoustin]). * Add verbose option for list-dsn, add tests and clean up code (Thanks: [Dick Marinus]). -Bug Fixes: +Bug Fixes ---------- * Add enable_pager to the config file (Thanks: [Frederic Aoustin]). @@ -414,51 +423,50 @@ Bug Fixes: * Quote CSV fields (Thanks: [Thomas Roten]). * Fix `thanks_picker` (Thanks: [Dick Marinus]). -Internal: +Internal --------- * Refactor Destructive Warning behave tests (Thanks: [Dick Marinus]). - -1.16.0: +1.16.0 ======= -Features: +Features --------- * Add DSN aliases to the config file (Thanks: [Frederic Aoustin]). -Bug Fixes: +Bug Fixes ---------- * Do not try to connect to a unix socket on Windows (Thanks: [Thomas Roten]). -1.15.0: +1.15.0 ======= -Features: +Features --------- * Add sql-update/insert output format. (Thanks: [Dick Marinus]). * Also complete aliases in WHERE. (Thanks: [Dick Marinus]). -1.14.0: +1.14.0 ======= -Features: +Features --------- * Add `watch [seconds] query` command to repeat a query every [seconds] seconds (by default 5). (Thanks: [David Caro](https://github.com/Terseus)) * Default to unix socket connection if host and port are unspecified. This simplifies authentication on some systems and matches mysql behaviour. * Add support for positional parameters to favorite queries. (Thanks: [Scrappy Soft](https://github.com/scrappysoft)) -Bug Fixes: +Bug Fixes ---------- * Fix source command for script in current working directory. (Thanks: [Dick Marinus]). * Fix issue where the `tee` command did not work on Python 2.7 (Thanks: [Thomas Roten]). -Internal Changes: +Internal Changes ----------------- * Drop support for Python 3.3 (Thanks: [Thomas Roten]). @@ -466,64 +474,63 @@ Internal Changes: * Make tests more compatible between different build environments. (Thanks: [David Caro]) * Merge `_on_completions_refreshed` and `_swap_completer_objects` functions (Thanks: [Dick Marinus]). -1.13.1: +1.13.1 ======= -Bug Fixes: +Bug Fixes ---------- * Fix keyword completion suggestion for `SHOW` (Thanks: [Thomas Roten]). * Prevent mycli from crashing when failing to read login path file (Thanks: [Thomas Roten]). -Internal Changes: +Internal Changes ----------------- * Make tests ignore user config files (Thanks: [Thomas Roten]). -1.13.0: +1.13.0 ======= -Features: +Features --------- * Add file name completion for source command (issue #500). (Thanks: [Irina Truong]). -Bug Fixes: +Bug Fixes ---------- * Fix UnicodeEncodeError when editing sql command in external editor (Thanks: Klaus Wünschel). * Fix MySQL4 version comment retrieval (Thanks: [François Pietka]) * Fix error that occurred when outputting JSON and NULL data (Thanks: [Thomas Roten]). -1.12.1: +1.12.1 ======= -Bug Fixes: +Bug Fixes ---------- * Prevent missing MySQL help database from causing errors in completions (Thanks: [Thomas Roten]). * Fix mycli from crashing with small terminal windows under Python 2 (Thanks: [Thomas Roten]). * Prevent an error from displaying when you drop the current database (Thanks: [Thomas Roten]). -Internal Changes: +Internal Changes ----------------- * Use less memory when formatting results for display (Thanks: [Dick Marinus]). * Preliminary work for a future change in outputting results that uses less memory (Thanks: [Dick Marinus]). -1.12.0: +1.12.0 ======= -Features: +Features --------- * Add fish-style auto-suggestion from history. (Thanks: [Amjith Ramanujam]) - -1.11.0: +1.11.0 ======= -Features: +Features --------- * Handle reserved space for completion menu better in small windows. (Thanks: [Thomas Roten]). @@ -537,7 +544,7 @@ Features: * Add colored/styled headers and odd/even rows (Thanks: [Thomas Roten]). * Keyword completion casing (upper/lower/auto) (Thanks: [Irina Truong]). -Bug Fixes: +Bug Fixes ---------- * Fixed incorrect timekeeping when running queries from a file. (Thanks: [Thomas Roten]). @@ -548,7 +555,7 @@ Bug Fixes: * Support tilde user directory for output file names (Thanks: [Thomas Roten]). * Auto vertical output is a little bit better at its calculations (Thanks: [Thomas Roten]). -Internal Changes: +Internal Changes ----------------- * Rename tests/ to test/. (Thanks: [Dick Marinus]). @@ -567,10 +574,10 @@ Internal Changes: * Add missing @dbtest to tests (Thanks: [Dick Marinus]). * Standardizes punctuation/grammar for help strings (Thanks: [Thomas Roten]). -1.10.0: +1.10.0 ======= -Features: +Features --------- * Add ability to specify alternative myclirc file. (Thanks: [Dick Marinus]). @@ -578,7 +585,7 @@ Features: Ramanujam], [Dick Marinus], [Thomas Roten]). * Add logic to shorten the default prompt if it becomes too long once generated. (Thanks: [John Sterling]). -Bug Fixes: +Bug Fixes ---------- * Fix external editor bug (issue #377). (Thanks: [Irina Truong]). @@ -589,7 +596,7 @@ Bug Fixes: (Thanks: [Thomas Roten]). * Use pymysql default conversions (issue #375). (Thanks: [Dick Marinus]). -Internal Changes: +Internal Changes ----------------- * Upload mycli distributions in a safer manner (using twine). (Thanks: [Thomas @@ -598,10 +605,10 @@ Internal Changes: * Run pep8 checks in travis (Thanks: [Irina Truong]). * Remove temporary hack for sqlparse (Thanks: [Dick Marinus]). -1.9.0: +1.9.0 ====== -Features: +Features --------- * Add tee/notee commands for outputing results to a file. (Thanks: [Dick Marinus]). @@ -612,7 +619,7 @@ Features: * Add `auto_vertical_output` config to myclirc. (Thanks: [Matheus Rosa]). * Improve Fedora install instructions. (Thanks: [Dick Marinus]). -Bug Fixes: +Bug Fixes ---------- * Fix crashes occuring from commands starting with #. (Thanks: [Zhidong]). @@ -624,7 +631,7 @@ Bug Fixes: * Kill running query when interrupted via Ctrl-C. (Thanks: [chainkite]). * Read the `smart_completion` config from myclirc. (Thanks: [Thomas Roten]). -Internal Changes: +Internal Changes ----------------- * Improve handling of test database credentials. (Thanks: [Dick Marinus]). @@ -633,25 +640,27 @@ Internal Changes: * Swap pycrypto dependency for pycryptodome. (Thanks: [Michał Górny]). * Bump sqlparse version so pgcli and mycli can be installed together. (Thanks: [darikg]). -1.8.1: +1.8.1 ====== -Bug Fixes: +Bug Fixes ---------- + * Remove duplicate listing of DISTINCT keyword. (Thanks: [Amjith Ramanujam]). * Add an try/except for AS keyword crash. (Thanks: [Amjith Ramanujam]). * Support python-sqlparse 0.2. (Thanks: [Dick Marinus]). * Fallback to the raw object for invalid time values. (Thanks: [Amjith Ramanujam]). * Reset the show items when completion is refreshed. (Thanks: [Amjith Ramanujam]). -Internal Changes: +Internal Changes ----------------- + * Make the dependency of sqlparse slightly more liberal. (Thanks: [Amjith Ramanujam]). -1.8.0: +1.8.0 ====== -Features: +Features --------- * Add support for --execute/-e commandline arg. (Thanks: [Matheus Rosa]). @@ -660,17 +669,17 @@ Features: * Add `prompt_continuation` config option to allow configuring the continuation prompt for multi-line queries. (Thanks: [Scrappy Soft]). * Display login-path instead of host in prompt. (Thanks: [Irina Truong]). -Bug Fixes: +Bug Fixes ---------- * Pin sqlparse to version 0.1.19 since the new version is breaking completion. (Thanks: [Amjith Ramanujam]). * Remove unsupported keywords. (Thanks: [Matheus Rosa]). * Fix completion suggestion inside functions with operands. (Thanks: [Irina Truong]). -1.7.0: +1.7.0 ====== -Features: +Features --------- * Add stdin batch mode. (Thanks: [Thomas Roten]). @@ -679,20 +688,20 @@ Features: * Update features list in README.md. (Thanks: [Matheus Rosa]). * Remove extra \n in features list in README.md. (Thanks: [Matheus Rosa]). -Bug Fixes: +Bug Fixes ---------- * Enable history search via <C-r>. (Thanks: [Amjith Ramanujam]). -Internal Changes: +Internal Changes ----------------- * Upgrade `prompt_toolkit` to 1.0.0. (Thanks: [Jonathan Slenders]) -1.6.0: +1.6.0 ====== -Features: +Features --------- * Change continuation prompt for multi-line mode to match default mysql. @@ -705,14 +714,14 @@ Features: * Add support for `nopager` and `\n` to turn off the pager. (Thanks: [Thomas Roten]). * Add support for `--local-infile` command-line option. (Thanks: [Thomas Roten]). -Bug Fixes: +Bug Fixes ---------- * Remove -S from `less` option which was clobbering the scroll back in history. (Thanks: [Thomas Roten]). * Make system command work with Python 3. (Thanks: [Thomas Roten]). * Support \G terminator for \f queries. (Thanks: [Terseus]). -Internal Changes: +Internal Changes ----------------- * Upgrade `prompt_toolkit` to 0.60. @@ -723,26 +732,26 @@ Internal Changes: * Capture warnings to log file. (Thanks: [Mikhail Borisov]). * Make `syntax_style` a tiny bit more intuitive. (Thanks: [Phil Cohen]). -1.5.2: +1.5.2 ====== -Bug Fixes: +Bug Fixes ---------- * Protect against port number being None when no port is specified in command line. -1.5.1: +1.5.1 ====== -Bug Fixes: +Bug Fixes ---------- * Cast the value of port read from my.cnf to int. -1.5.0: +1.5.0 ====== -Features: +Features --------- * Make a config option to enable `audit_log`. (Thanks: [Matheus Rosa]). @@ -751,21 +760,25 @@ Features: * Register the special command `prompt` with the `\R` as alias. (Thanks: [Matheus Rosa]). Users can now change the mysql prompt at runtime using `prompt` command. eg: + ``` mycli> prompt \u@\h> Changed prompt format to \u@\h> Time: 0.001s amjith@localhost> ``` + * Perform completion refresh in a background thread. Now mycli can handle databases with thousands of tables without blocking. * Add support for `system` command. (Thanks: [Matheus Rosa]). Users can now run a system command from within mycli as follows: + ``` amjith@localhost:(none)>system cat tmp.sql select 1; select * from django_migrations; ``` + * Caught and hexed binary fields in MySQL. (Thanks: [Daniel West]). Geometric fields stored in a database will be displayed as hexed strings. * Treat enter key as tab when the suggestion menu is open. (Thanks: [Matheus Rosa]) @@ -775,7 +788,7 @@ Features: * Add TRANSACTION related keywords. * Treat DESC and EXPLAIN as DESCRIBE. (Thanks: [spacewander]). -Bug Fixes: +Bug Fixes ---------- * Fix the removal of whitespace from table output. @@ -783,23 +796,25 @@ Bug Fixes: * Fix the incorrect reporting of command time. * Add type validation for port argument. (Thanks [Matheus Rosa]) -Internal Changes: +Internal Changes ----------------- + * Make pycrypto optional and only install it in \*nix systems. (Thanks: [Irina Truong]). * Add badge for PyPI version to README. (Thanks: [Shoma Suzuki]). * Updated release script with a --dry-run and --confirm-steps option. (Thanks: [Irina Truong]). * Adds support for PyMySQL 0.6.2 and above. This is useful for debian package builders. (Thanks: [Thomas Roten]). * Disable click warning. -1.4.0: +1.4.0 ====== -Features: +Features --------- * Add `source` command. This allows running sql statement from a file. eg: + ``` mycli> source filename.sql ``` @@ -818,29 +833,33 @@ Features: Multi-line queries are automatically indented. -Bug Fixes: +Bug Fixes ---------- * Fix keyword completion after the `WHERE` clause. * Add `\g` and `\G` as valid query terminators. Previously in multi-line mode ending a query with a `\G` wouldn't run the query. This is now fixed. -1.3.0: +1.3.0 ====== -Features: +Features --------- + * Add a new special command (\T) to change the table format on the fly. (Thanks: [Jonathan Bruno](https://github.com/brewneaux)) eg: + ``` mycli> \T tsv ``` + * Add `--defaults-group-suffix` to the command line. This lets the user specify a group to use in the my.cnf files. (Thanks: [Irina Truong](http://github.com/j-bennet)) In the my.cnf file a user can specify credentials for different databases and invoke mycli with the group name to use the appropriate credentials. eg: + ``` # my.cnf [client] @@ -862,79 +881,77 @@ Features: * Make `-p` and `--password` take the password in commandline. This makes mycli a drop in replacement for mysql. -1.2.0: +1.2.0 ====== -Features: +Features --------- * Add support for wider completion menus in the config file. Add `wider_completion_menu = True` in the config file (~/.myclirc) to enable this feature. -Bug Fixes: +Bug Fixes --------- * Prevent Ctrl-C from quitting mycli while the pager is active. * Refresh auto-completions after the database is changed via a CONNECT command. -Internal Changes: +Internal Changes ----------------- * Upgrade `prompt_toolkit` dependency version to 0.45. * Added Travis CI to run the tests automatically. -1.1.1: +1.1.1 ====== -Bug Fixes: +Bug Fixes ---------- * Change dictonary comprehension used in mycnf reader to list comprehension to make it compatible with Python 2.6. - -1.1.0: +1.1.0 ====== -Features: +Features --------- * Fuzzy completion is now case-insensitive. (Thanks: [bjarnagin](https://github.com/bjarnagin)) * Added new-line (`\n`) to the list of special characters to use in prompt. (Thanks: [brewneaux](https://github.com/brewneaux)) * Honor the `pager` setting in my.cnf files. (Thanks: [Irina Truong](http://github.com/j-bennet)) -Bug Fixes: +Bug Fixes ---------- * Fix a crashing bug in completion engine for cross joins. * Make `<null>` value consistent between tabular and vertical output. -Internal Changes: +Internal Changes ----------------- * Changed pymysql version to be greater than 0.6.6. * Upgrade `prompt_toolkit` version to 0.42. (Thanks: [Yasuhiro Matsumoto](https://github.com/mattn)) * Removed the explicit dependency on six. -2015/06/10: +2015/06/10 =========== -Features: +Features --------- * Customizable prompt. (Thanks [Steve Robbins](https://github.com/steverobbins)) * Make `\G` formatting to behave more like mysql. -Bug Fixes: +Bug Fixes ---------- * Formatting issue in \G for really long column values. - -2015/06/07: +2015/06/07 =========== -Features: +Features --------- * Upgrade `prompt_toolkit` to 0.38. This improves the performance of pasting long queries. @@ -945,18 +962,17 @@ Features: * Add fuzzy completion for table names and column names. * Automatically reconnect when connection is lost to the database. -Bug Fixes: +Bug Fixes ---------- * Fix a bug with reconnect failure. * Fix the issue with `use` command not changing the prompt. * Fix the issue where `\\r` shortcut was not recognized. - 2015/05/24 ========== -Features: +Features --------- * Add support for connecting via socket. @@ -965,15 +981,14 @@ Features: * Made the timing of sql statements human friendly. * Automatically prompt for a password if needed. -Bug Fixes: +Bug Fixes ---------- + * Fixed the installation issues with PyMySQL dependency on case-sensitive file systems. [Amjith Ramanujam]: https://blog.amjith.com [Artem Bezsmertnyi]: https://github.com/mrdeathless [BuonOmo]: https://github.com/BuonOmo -[Carlos Afonso]: https://github.com/afonsocarlos -[Casper Langemeijer]: https://github.com/langemeijer [Daniel West]: http://github.com/danieljwest [Dick Marinus]: https://github.com/meeuw [François Pietka]: https://github.com/fpietka @@ -981,9 +996,7 @@ Bug Fixes: [Georgy Frolov]: https://github.com/pasenor [Irina Truong]: https://github.com/j-bennet [Jonathan Slenders]: https://github.com/jonathanslenders -[Kacper Kwapisz]: https://github.com/KKKas [laixintao]: https://github.com/laixintao -[Lennart Weller]: https://github.com/lhw [Martijn Engler]: https://github.com/martijnengler [Matheus Rosa]: https://github.com/mdsrosa [Mikhail Borisov]: https://github.com/borman @@ -995,7 +1008,6 @@ Bug Fixes: [spacewander]: https://github.com/spacewander [Terseus]: https://github.com/Terseus [Thomas Roten]: https://github.com/tsroten -[William GARCIA]: https://github.com/willgarcia [xeron]: https://github.com/xeron [Zach DeCook]: https://zachdecook.com [Will Wang]: https://github.com/willww64 diff --git a/mycli/AUTHORS b/mycli/AUTHORS index e9c73aa..d5a9ce0 100644 --- a/mycli/AUTHORS +++ b/mycli/AUTHORS @@ -96,6 +96,8 @@ Contributors: * Alfred Wingate * Zhanze Wang * Houston Wong + * Mohamed Rezk + * Ryosuke Kazami Created by: diff --git a/mycli/__init__.py b/mycli/__init__.py index b5476c1..b3f408d 100644 --- a/mycli/__init__.py +++ b/mycli/__init__.py @@ -1 +1 @@ -__version__ = '1.27.2' +__version__ = "1.28.0" diff --git a/mycli/key_bindings.py b/mycli/key_bindings.py index 443233f..b084849 100644 --- a/mycli/key_bindings.py +++ b/mycli/key_bindings.py @@ -3,6 +3,8 @@ from prompt_toolkit.enums import EditingMode from prompt_toolkit.filters import completion_is_selected, emacs_mode from prompt_toolkit.key_binding import KeyBindings +from .packages.toolkit.fzf import search_history + _logger = logging.getLogger(__name__) @@ -101,6 +103,12 @@ def mycli_bindings(mycli): cursorpos_abs -= 1 b.cursor_position = min(cursorpos_abs, len(b.text)) + @kb.add('c-r', filter=emacs_mode) + def _(event): + """Search history using fzf or default reverse incremental search.""" + _logger.debug('Detected <C-r> key.') + search_history(event) + @kb.add('enter', filter=completion_is_selected) def _(event): """Makes the enter key work as the tab key only when showing the menu. diff --git a/mycli/main.py b/mycli/main.py index ce4dff7..4c194ce 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -36,7 +36,6 @@ from prompt_toolkit.formatted_text import ANSI from prompt_toolkit.layout.processors import (HighlightMatchingBracketProcessor, ConditionalProcessor) from prompt_toolkit.lexers import PygmentsLexer -from prompt_toolkit.history import FileHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from .packages.special.main import NO_QUERY @@ -44,6 +43,7 @@ from .packages.prompt_utils import confirm, confirm_destructive_query from .packages.tabular_output import sql_format from .packages import special from .packages.special.favoritequeries import FavoriteQueries +from .packages.toolkit.history import FileHistoryWithTimestamp from .sqlcompleter import SQLCompleter from .clitoolbar import create_toolbar_tokens_func from .clistyle import style_factory, style_factory_output @@ -626,7 +626,7 @@ class MyCli(object): history_file = os.path.expanduser( os.environ.get('MYCLI_HISTFILE', '~/.mycli-history')) if dir_path_exists(history_file): - history = FileHistory(history_file) + history = FileHistoryWithTimestamp(history_file) else: history = None self.echo( diff --git a/mycli/packages/completion_engine.py b/mycli/packages/completion_engine.py index 2735f5b..6d5709a 100644 --- a/mycli/packages/completion_engine.py +++ b/mycli/packages/completion_engine.py @@ -138,6 +138,8 @@ def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier if not token: return [{'type': 'keyword'}, {'type': 'special'}] + elif token_v == "*": + return [{'type': 'keyword'}] elif token_v.endswith('('): p = sqlparse.parse(text_before_cursor)[0] diff --git a/mycli/packages/filepaths.py b/mycli/packages/filepaths.py index 79fe26d..a91055d 100644 --- a/mycli/packages/filepaths.py +++ b/mycli/packages/filepaths.py @@ -100,7 +100,7 @@ def guess_socket_location(): for r, dirs, files in os.walk(directory, topdown=True): for filename in files: name, ext = os.path.splitext(filename) - if name.startswith("mysql") and ext in ('.socket', '.sock'): + if name.startswith("mysql") and name != "mysqlx" and ext in ('.socket', '.sock'): return os.path.join(r, filename) dirs[:] = [d for d in dirs if d.startswith("mysql")] return None diff --git a/mycli/packages/toolkit/__init__.py b/mycli/packages/toolkit/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/mycli/packages/toolkit/__init__.py diff --git a/mycli/packages/toolkit/fzf.py b/mycli/packages/toolkit/fzf.py new file mode 100644 index 0000000..36cb347 --- /dev/null +++ b/mycli/packages/toolkit/fzf.py @@ -0,0 +1,45 @@ +from shutil import which + +from pyfzf import FzfPrompt +from prompt_toolkit import search +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + +from .history import FileHistoryWithTimestamp + + +class Fzf(FzfPrompt): + def __init__(self): + self.executable = which("fzf") + if self.executable: + super().__init__() + + def is_available(self) -> bool: + return self.executable is not None + + +def search_history(event: KeyPressEvent): + buffer = event.current_buffer + history = buffer.history + + fzf = Fzf() + + if fzf.is_available() and isinstance(history, FileHistoryWithTimestamp): + history_items_with_timestamp = history.load_history_with_timestamp() + + formatted_history_items = [] + original_history_items = [] + for item, timestamp in history_items_with_timestamp: + formatted_item = item.replace('\n', ' ') + timestamp = timestamp.split(".")[0] if "." in timestamp else timestamp + formatted_history_items.append(f"{timestamp} {formatted_item}") + original_history_items.append(item) + + result = fzf.prompt(formatted_history_items, fzf_options="--tiebreak=index") + + if result: + selected_index = formatted_history_items.index(result[0]) + buffer.text = original_history_items[selected_index] + buffer.cursor_position = len(buffer.text) + else: + # Fallback to default reverse incremental search + search.start_search(direction=search.SearchDirection.BACKWARD) diff --git a/mycli/packages/toolkit/history.py b/mycli/packages/toolkit/history.py new file mode 100644 index 0000000..75f4a5a --- /dev/null +++ b/mycli/packages/toolkit/history.py @@ -0,0 +1,52 @@ +import os +from typing import Iterable, Union, List, Tuple + +from prompt_toolkit.history import FileHistory + +_StrOrBytesPath = Union[str, bytes, os.PathLike] + + +class FileHistoryWithTimestamp(FileHistory): + """ + :class:`.FileHistory` class that stores all strings in a file with timestamp. + """ + + def __init__(self, filename: _StrOrBytesPath) -> None: + self.filename = filename + super().__init__(filename) + + def load_history_with_timestamp(self) -> List[Tuple[str, str]]: + """ + Load history entries along with their timestamps. + + Returns: + List[Tuple[str, str]]: A list of tuples where each tuple contains + a history entry and its corresponding timestamp. + """ + history_with_timestamp: List[Tuple[str, str]] = [] + lines: List[str] = [] + timestamp: str = "" + + def add() -> None: + if lines: + # Join and drop trailing newline. + string = "".join(lines)[:-1] + history_with_timestamp.append((string, timestamp)) + + if os.path.exists(self.filename): + with open(self.filename, "rb") as f: + for line_bytes in f: + line = line_bytes.decode("utf-8", errors="replace") + + if line.startswith("#"): + # Extract timestamp + timestamp = line[2:].strip() + elif line.startswith("+"): + lines.append(line[1:]) + else: + add() + lines = [] + + add() + + return list(reversed(history_with_timestamp)) diff --git a/mycli/sqlcompleter.py b/mycli/sqlcompleter.py index ab42fb8..b0eecea 100644 --- a/mycli/sqlcompleter.py +++ b/mycli/sqlcompleter.py @@ -31,7 +31,7 @@ class SQLCompleter(Completer): 'PORT', 'PRIMARY', 'PRIVILEGES', 'PROCESSLIST', 'PURGE', 'REFERENCES', 'REGEXP', 'RENAME', 'REPAIR', 'RESET', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROW', 'ROWS', 'ROW_FORMAT', 'SAVEPOINT', - 'SESSION', 'SET', 'SHARE', 'SHOW', 'SLAVE', 'SMALLINT', 'SMALLINT', + 'SESSION', 'SET', 'SHARE', 'SHOW', 'SLAVE', 'SMALLINT', 'START', 'STOP', 'TABLE', 'THEN', 'TINYINT', 'TO', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'UNION', 'UNIQUE', 'UNSIGNED', 'USE', 'USER', 'USING', 'VALUES', 'VARCHAR', 'VIEW', 'WHEN', 'WITH' @@ -475,8 +475,6 @@ class SQLCompleter(Completer): elif suggestion['type'] == 'keyword': keywords = self.find_matches(word_before_cursor, self.keywords, - start_only=True, - fuzzy=False, casing=self.keyword_casing) completions.extend(keywords) @@ -513,8 +511,8 @@ class SQLCompleter(Completer): completions.extend(queries) elif suggestion['type'] == 'table_format': formats = self.find_matches(word_before_cursor, - self.table_formats, - start_only=True, fuzzy=False) + self.table_formats) + completions.extend(formats) elif suggestion['type'] == 'file_name': file_names = self.find_files(word_before_cursor) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3f5fbdf..603efa2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,4 +14,4 @@ pyperclip>=1.8.1 importlib_resources>=5.0.0 pyaes>=1.6.1 sqlglot>=5.1.3 -setuptools +setuptools<=71.1.0 @@ -29,7 +29,8 @@ install_requirements = [ 'configobj >= 5.0.5', 'cli_helpers[styles] >= 2.2.1', 'pyperclip >= 1.8.1', - 'pyaes >= 1.6.1' + 'pyaes >= 1.6.1', + 'pyfzf >= 0.3.1', ] if sys.version_info.minor < 9: diff --git a/test/myclirc b/test/myclirc index 0c1a7ad..7d96c45 100644 --- a/test/myclirc +++ b/test/myclirc @@ -89,6 +89,9 @@ keyword_casing = auto # disabled pager on startup enable_pager = True +# Choose a specific pager +pager = less + # Custom colors for the completion menu, toolbar, etc. [colors] completion-menu.completion.current = "bg:#ffffff #000000" diff --git a/test/test_smart_completion_public_schema_only.py b/test/test_smart_completion_public_schema_only.py index b60e67c..30b15ac 100644 --- a/test/test_smart_completion_public_schema_only.py +++ b/test/test_smart_completion_public_schema_only.py @@ -5,17 +5,17 @@ from prompt_toolkit.document import Document import mycli.packages.special.main as special metadata = { - 'users': ['id', 'email', 'first_name', 'last_name'], - 'orders': ['id', 'ordered_date', 'status'], - 'select': ['id', 'insert', 'ABC'], - 'réveillé': ['id', 'insert', 'ABC'] + "users": ["id", "email", "first_name", "last_name"], + "orders": ["id", "ordered_date", "status"], + "select": ["id", "insert", "ABC"], + "réveillé": ["id", "insert", "ABC"], } @pytest.fixture def completer(): - import mycli.sqlcompleter as sqlcompleter + comp = sqlcompleter.SQLCompleter(smart_completion=True) tables, columns = [], [] @@ -24,10 +24,10 @@ def completer(): tables.append((table,)) columns.extend([(table, col) for col in cols]) - comp.set_dbname('test') - comp.extend_schemata('test') - comp.extend_relations(tables, kind='tables') - comp.extend_columns(columns, kind='tables') + comp.set_dbname("test") + comp.extend_schemata("test") + comp.extend_relations(tables, kind="tables") + comp.extend_columns(columns, kind="tables") comp.extend_special_commands(special.COMMANDS) return comp @@ -36,59 +36,85 @@ def completer(): @pytest.fixture def complete_event(): from unittest.mock import Mock + return Mock() def test_special_name_completion(completer, complete_event): - text = '\\d' - position = len('\\d') + text = "\\d" + position = len("\\d") result = completer.get_completions( - Document(text=text, cursor_position=position), - complete_event) - assert result == [Completion(text='\\dt', start_position=-2)] + Document(text=text, cursor_position=position), complete_event + ) + assert result == [Completion(text="\\dt", start_position=-2)] def test_empty_string_completion(completer, complete_event): - text = '' + text = "" position = 0 result = list( completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert list(map(Completion, completer.keywords + - completer.special_commands)) == result + Document(text=text, cursor_position=position), complete_event + ) + ) + assert ( + list(map(Completion, completer.keywords + completer.special_commands)) == result + ) def test_select_keyword_completion(completer, complete_event): - text = 'SEL' - position = len('SEL') + text = "SEL" + position = len("SEL") result = completer.get_completions( - Document(text=text, cursor_position=position), - complete_event) - assert list(result) == list([Completion(text='SELECT', start_position=-3)]) + Document(text=text, cursor_position=position), complete_event + ) + assert list(result) == list([Completion(text="SELECT", start_position=-3)]) + + +def test_select_star(completer, complete_event): + text = "SELECT * " + position = len(text) + result = completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + assert list(result) == list(map(Completion, completer.keywords)) def test_table_completion(completer, complete_event): - text = 'SELECT * FROM ' + text = "SELECT * FROM " position = len(text) result = completer.get_completions( - Document(text=text, cursor_position=position), complete_event) - assert list(result) == list([ - Completion(text='users', start_position=0), - Completion(text='orders', start_position=0), - Completion(text='`select`', start_position=0), - Completion(text='`réveillé`', start_position=0), - ]) + Document(text=text, cursor_position=position), complete_event + ) + assert list(result) == list( + [ + Completion(text="users", start_position=0), + Completion(text="orders", start_position=0), + Completion(text="`select`", start_position=0), + Completion(text="`réveillé`", start_position=0), + ] + ) def test_function_name_completion(completer, complete_event): - text = 'SELECT MA' - position = len('SELECT MA') + text = "SELECT MA" + position = len("SELECT MA") result = completer.get_completions( - Document(text=text, cursor_position=position), complete_event) - assert list(result) == list([Completion(text='MAX', start_position=-2), - Completion(text='MASTER', start_position=-2), - ]) + Document(text=text, cursor_position=position), complete_event + ) + assert list(result) == list( + [ + Completion(text="MAX", start_position=-2), + Completion(text="CHANGE MASTER TO", start_position=-2), + Completion(text="CURRENT_TIMESTAMP", start_position=-2), + Completion(text="DECIMAL", start_position=-2), + Completion(text="FORMAT", start_position=-2), + Completion(text="MASTER", start_position=-2), + Completion(text="PRIMARY", start_position=-2), + Completion(text="ROW_FORMAT", start_position=-2), + Completion(text="SMALLINT", start_position=-2), + ] + ) def test_suggested_column_names(completer, complete_event): @@ -99,21 +125,25 @@ def test_suggested_column_names(completer, complete_event): :return: """ - text = 'SELECT from users' - position = len('SELECT ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0), - ] + - list(map(Completion, completer.functions)) + - [Completion(text='users', start_position=0)] + - list(map(Completion, completer.keywords))) + text = "SELECT from users" + position = len("SELECT ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + + list(map(Completion, completer.functions)) + + [Completion(text="users", start_position=0)] + + list(map(Completion, completer.keywords)) + ) def test_suggested_column_names_in_function(completer, complete_event): @@ -125,17 +155,20 @@ def test_suggested_column_names_in_function(completer, complete_event): :return: """ - text = 'SELECT MAX( from users' - position = len('SELECT MAX(') + text = "SELECT MAX( from users" + position = len("SELECT MAX(") result = completer.get_completions( - Document(text=text, cursor_position=position), - complete_event) - assert list(result) == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0)]) + Document(text=text, cursor_position=position), complete_event + ) + assert list(result) == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + ) def test_suggested_column_names_with_table_dot(completer, complete_event): @@ -146,17 +179,22 @@ def test_suggested_column_names_with_table_dot(completer, complete_event): :return: """ - text = 'SELECT users. from users' - position = len('SELECT users.') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0)]) + text = "SELECT users. from users" + position = len("SELECT users.") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + ) def test_suggested_column_names_with_alias(completer, complete_event): @@ -167,17 +205,22 @@ def test_suggested_column_names_with_alias(completer, complete_event): :return: """ - text = 'SELECT u. from users u' - position = len('SELECT u.') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0)]) + text = "SELECT u. from users u" + position = len("SELECT u.") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + ) def test_suggested_multiple_column_names(completer, complete_event): @@ -189,20 +232,25 @@ def test_suggested_multiple_column_names(completer, complete_event): :return: """ - text = 'SELECT id, from users u' - position = len('SELECT id, ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0)] + - list(map(Completion, completer.functions)) + - [Completion(text='u', start_position=0)] + - list(map(Completion, completer.keywords))) + text = "SELECT id, from users u" + position = len("SELECT id, ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + + list(map(Completion, completer.functions)) + + [Completion(text="u", start_position=0)] + + list(map(Completion, completer.keywords)) + ) def test_suggested_multiple_column_names_with_alias(completer, complete_event): @@ -214,17 +262,22 @@ def test_suggested_multiple_column_names_with_alias(completer, complete_event): :return: """ - text = 'SELECT u.id, u. from users u' - position = len('SELECT u.id, u.') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0)]) + text = "SELECT u.id, u. from users u" + position = len("SELECT u.id, u.") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + ) def test_suggested_multiple_column_names_with_dot(completer, complete_event): @@ -236,154 +289,185 @@ def test_suggested_multiple_column_names_with_dot(completer, complete_event): :return: """ - text = 'SELECT users.id, users. from users u' - position = len('SELECT users.id, users.') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='email', start_position=0), - Completion(text='first_name', start_position=0), - Completion(text='last_name', start_position=0)]) + text = "SELECT users.id, users. from users u" + position = len("SELECT users.id, users.") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="email", start_position=0), + Completion(text="first_name", start_position=0), + Completion(text="last_name", start_position=0), + ] + ) def test_suggested_aliases_after_on(completer, complete_event): - text = 'SELECT u.name, o.id FROM users u JOIN orders o ON ' - position = len('SELECT u.name, o.id FROM users u JOIN orders o ON ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='u', start_position=0), - Completion(text='o', start_position=0), - ]) + text = "SELECT u.name, o.id FROM users u JOIN orders o ON " + position = len("SELECT u.name, o.id FROM users u JOIN orders o ON ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="u", start_position=0), + Completion(text="o", start_position=0), + ] + ) def test_suggested_aliases_after_on_right_side(completer, complete_event): - text = 'SELECT u.name, o.id FROM users u JOIN orders o ON o.user_id = ' - position = len( - 'SELECT u.name, o.id FROM users u JOIN orders o ON o.user_id = ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='u', start_position=0), - Completion(text='o', start_position=0), - ]) + text = "SELECT u.name, o.id FROM users u JOIN orders o ON o.user_id = " + position = len("SELECT u.name, o.id FROM users u JOIN orders o ON o.user_id = ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="u", start_position=0), + Completion(text="o", start_position=0), + ] + ) def test_suggested_tables_after_on(completer, complete_event): - text = 'SELECT users.name, orders.id FROM users JOIN orders ON ' - position = len('SELECT users.name, orders.id FROM users JOIN orders ON ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='users', start_position=0), - Completion(text='orders', start_position=0), - ]) + text = "SELECT users.name, orders.id FROM users JOIN orders ON " + position = len("SELECT users.name, orders.id FROM users JOIN orders ON ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="users", start_position=0), + Completion(text="orders", start_position=0), + ] + ) def test_suggested_tables_after_on_right_side(completer, complete_event): - text = 'SELECT users.name, orders.id FROM users JOIN orders ON orders.user_id = ' + text = "SELECT users.name, orders.id FROM users JOIN orders ON orders.user_id = " position = len( - 'SELECT users.name, orders.id FROM users JOIN orders ON orders.user_id = ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='users', start_position=0), - Completion(text='orders', start_position=0), - ]) + "SELECT users.name, orders.id FROM users JOIN orders ON orders.user_id = " + ) + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="users", start_position=0), + Completion(text="orders", start_position=0), + ] + ) def test_table_names_after_from(completer, complete_event): - text = 'SELECT * FROM ' - position = len('SELECT * FROM ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='users', start_position=0), - Completion(text='orders', start_position=0), - Completion(text='`select`', start_position=0), - Completion(text='`réveillé`', start_position=0), - ]) + text = "SELECT * FROM " + position = len("SELECT * FROM ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="users", start_position=0), + Completion(text="orders", start_position=0), + Completion(text="`select`", start_position=0), + Completion(text="`réveillé`", start_position=0), + ] + ) def test_auto_escaped_col_names(completer, complete_event): - text = 'SELECT from `select`' - position = len('SELECT ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) + text = "SELECT from `select`" + position = len("SELECT ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) assert result == [ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='`insert`', start_position=0), - Completion(text='`ABC`', start_position=0), - ] + \ - list(map(Completion, completer.functions)) + \ - [Completion(text='select', start_position=0)] + \ - list(map(Completion, completer.keywords)) + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="`insert`", start_position=0), + Completion(text="`ABC`", start_position=0), + ] + list(map(Completion, completer.functions)) + [ + Completion(text="select", start_position=0) + ] + list(map(Completion, completer.keywords)) def test_un_escaped_table_names(completer, complete_event): - text = 'SELECT from réveillé' - position = len('SELECT ') - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) - assert result == list([ - Completion(text='*', start_position=0), - Completion(text='id', start_position=0), - Completion(text='`insert`', start_position=0), - Completion(text='`ABC`', start_position=0), - ] + - list(map(Completion, completer.functions)) + - [Completion(text='réveillé', start_position=0)] + - list(map(Completion, completer.keywords))) + text = "SELECT from réveillé" + position = len("SELECT ") + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) + assert result == list( + [ + Completion(text="*", start_position=0), + Completion(text="id", start_position=0), + Completion(text="`insert`", start_position=0), + Completion(text="`ABC`", start_position=0), + ] + + list(map(Completion, completer.functions)) + + [Completion(text="réveillé", start_position=0)] + + list(map(Completion, completer.keywords)) + ) def dummy_list_path(dir_name): dirs = { - '/': [ - 'dir1', - 'file1.sql', - 'file2.sql', + "/": [ + "dir1", + "file1.sql", + "file2.sql", ], - '/dir1': [ - 'subdir1', - 'subfile1.sql', - 'subfile2.sql', + "/dir1": [ + "subdir1", + "subfile1.sql", + "subfile2.sql", ], - '/dir1/subdir1': [ - 'lastfile.sql', + "/dir1/subdir1": [ + "lastfile.sql", ], } return dirs.get(dir_name, []) -@patch('mycli.packages.filepaths.list_path', new=dummy_list_path) -@pytest.mark.parametrize('text,expected', [ - # ('source ', [('~', 0), - # ('/', 0), - # ('.', 0), - # ('..', 0)]), - ('source /', [('dir1', 0), - ('file1.sql', 0), - ('file2.sql', 0)]), - ('source /dir1/', [('subdir1', 0), - ('subfile1.sql', 0), - ('subfile2.sql', 0)]), - ('source /dir1/subdir1/', [('lastfile.sql', 0)]), -]) +@patch("mycli.packages.filepaths.list_path", new=dummy_list_path) +@pytest.mark.parametrize( + "text,expected", + [ + # ('source ', [('~', 0), + # ('/', 0), + # ('.', 0), + # ('..', 0)]), + ("source /", [("dir1", 0), ("file1.sql", 0), ("file2.sql", 0)]), + ("source /dir1/", [("subdir1", 0), ("subfile1.sql", 0), ("subfile2.sql", 0)]), + ("source /dir1/subdir1/", [("lastfile.sql", 0)]), + ], +) def test_file_name_completion(completer, complete_event, text, expected): position = len(text) - result = list(completer.get_completions( - Document(text=text, cursor_position=position), - complete_event)) + result = list( + completer.get_completions( + Document(text=text, cursor_position=position), complete_event + ) + ) expected = list((Completion(txt, pos) for txt, pos in expected)) assert result == expected |