Merging upstream version 1.26.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
79468558b6
commit
c35ab76feb
23 changed files with 328 additions and 52 deletions
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
@ -9,16 +9,16 @@ jobs:
|
||||||
linux:
|
linux:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||||
include:
|
include:
|
||||||
- python-version: 3.6
|
- python-version: '3.7'
|
||||||
os: ubuntu-18.04 # MySQL 5.7.32
|
os: ubuntu-18.04 # MySQL 5.7.32
|
||||||
- python-version: 3.7
|
- python-version: '3.8'
|
||||||
os: ubuntu-18.04 # MySQL 5.7.32
|
os: ubuntu-18.04 # MySQL 5.7.32
|
||||||
- python-version: 3.8
|
- python-version: '3.9'
|
||||||
os: ubuntu-18.04 # MySQL 5.7.32
|
|
||||||
- python-version: 3.9
|
|
||||||
os: ubuntu-20.04 # MySQL 8.0.22
|
os: ubuntu-20.04 # MySQL 8.0.22
|
||||||
|
- python-version: '3.10'
|
||||||
|
os: ubuntu-22.04 # MySQL 8.0.28
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -95,7 +95,7 @@ credentials to use by setting the applicable environment variables:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ export PYTEST_HOST=localhost
|
$ export PYTEST_HOST=localhost
|
||||||
$ export PYTEST_USER=user
|
$ export PYTEST_USER=mycli
|
||||||
$ export PYTEST_PASSWORD=myclirocks
|
$ export PYTEST_PASSWORD=myclirocks
|
||||||
$ export PYTEST_PORT=3306
|
$ export PYTEST_PORT=3306
|
||||||
$ export PYTEST_CHARSET=utf8
|
$ export PYTEST_CHARSET=utf8
|
||||||
|
@ -104,6 +104,14 @@ $ export PYTEST_CHARSET=utf8
|
||||||
The default values are `localhost`, `root`, no password, `3306`, and `utf8`.
|
The default values are `localhost`, `root`, no password, `3306`, and `utf8`.
|
||||||
You only need to set the values that differ from the defaults.
|
You only need to set the values that differ from the defaults.
|
||||||
|
|
||||||
|
If you would like to run the tests as a user with only the necessary privileges,
|
||||||
|
create a `mycli` user and run the following grant statements.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
GRANT ALL PRIVILEGES ON `mycli_%`.* TO 'mycli'@'localhost';
|
||||||
|
GRANT SELECT ON mysql.* TO 'mycli'@'localhost';
|
||||||
|
GRANT SELECT ON performance_schema.* TO 'mycli'@'localhost';
|
||||||
|
```
|
||||||
|
|
||||||
### CLI Tests
|
### CLI Tests
|
||||||
|
|
||||||
|
|
25
README.md
25
README.md
|
@ -69,6 +69,8 @@ $ sudo apt-get install mycli # Only on debian or ubuntu
|
||||||
--ssh-config-host TEXT Host to connect to ssh server reading from ssh
|
--ssh-config-host TEXT Host to connect to ssh server reading from ssh
|
||||||
configuration.
|
configuration.
|
||||||
|
|
||||||
|
--ssl Enable SSL for connection (automatically
|
||||||
|
enabled with other flags).
|
||||||
--ssl-ca PATH CA file in PEM format.
|
--ssl-ca PATH CA file in PEM format.
|
||||||
--ssl-capath TEXT CA directory.
|
--ssl-capath TEXT CA directory.
|
||||||
--ssl-cert PATH X509 cert in PEM format.
|
--ssl-cert PATH X509 cert in PEM format.
|
||||||
|
@ -133,6 +135,7 @@ Features
|
||||||
* Log every query and its results to a file (disabled by default).
|
* Log every query and its results to a file (disabled by default).
|
||||||
* Pretty prints tabular data (with colors!)
|
* Pretty prints tabular data (with colors!)
|
||||||
* Support for SSL connections
|
* Support for SSL connections
|
||||||
|
* Some features are only exposed as [key bindings](doc/key_bindings.rst)
|
||||||
|
|
||||||
Contributions:
|
Contributions:
|
||||||
--------------
|
--------------
|
||||||
|
@ -151,6 +154,22 @@ Twitter: [@amjithr](http://twitter.com/amjithr)
|
||||||
|
|
||||||
## Detailed Install Instructions:
|
## Detailed Install Instructions:
|
||||||
|
|
||||||
|
### Arch, Manjaro
|
||||||
|
|
||||||
|
You can install the mycli package available in the AUR:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ yay -S mycli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debian, Ubuntu
|
||||||
|
|
||||||
|
On Debian, Ubuntu distributions, you can easily install the mycli package using apt:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install mycli
|
||||||
|
```
|
||||||
|
|
||||||
### Fedora
|
### Fedora
|
||||||
|
|
||||||
Fedora has a package available for mycli, install it using dnf:
|
Fedora has a package available for mycli, install it using dnf:
|
||||||
|
@ -164,13 +183,13 @@ $ sudo dnf install mycli
|
||||||
I haven't built an RPM package for mycli for RHEL or Centos yet. So please use `pip` to install `mycli`. You can install pip on your system using:
|
I haven't built an RPM package for mycli for RHEL or Centos yet. So please use `pip` to install `mycli`. You can install pip on your system using:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo yum install python-pip
|
$ sudo yum install python3-pip
|
||||||
```
|
```
|
||||||
|
|
||||||
Once that is installed, you can install mycli as follows:
|
Once that is installed, you can install mycli as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo pip install mycli
|
$ sudo pip3 install mycli
|
||||||
```
|
```
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
@ -201,7 +220,7 @@ Thanks to [PyMysql](https://github.com/PyMySQL/PyMySQL) for a pure python adapte
|
||||||
|
|
||||||
### Compatibility
|
### Compatibility
|
||||||
|
|
||||||
Mycli is tested on macOS and Linux.
|
Mycli is tested on macOS and Linux, and requires Python 3.7 or better.
|
||||||
|
|
||||||
**Mycli is not tested on Windows**, but the libraries used in this app are Windows-compatible.
|
**Mycli is not tested on Windows**, but the libraries used in this app are Windows-compatible.
|
||||||
This means it should work without any modifications. If you're unable to run it
|
This means it should work without any modifications. If you're unable to run it
|
||||||
|
|
35
changelog.md
35
changelog.md
|
@ -1,3 +1,36 @@
|
||||||
|
|
||||||
|
1.26.1 (2022/09/01)
|
||||||
|
===
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
----------
|
||||||
|
* Require Python 3.7 in `setup.py`
|
||||||
|
|
||||||
|
|
||||||
|
1.26.0 (2022/09/01)
|
||||||
|
===================
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
----------
|
||||||
|
* 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)
|
1.25.0 (2022/04/02)
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
@ -18,6 +51,7 @@ 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)
|
1.24.3 (2022/01/20)
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
@ -872,6 +906,7 @@ Bug Fixes:
|
||||||
|
|
||||||
[Amjith Ramanujam]: https://blog.amjith.com
|
[Amjith Ramanujam]: https://blog.amjith.com
|
||||||
[Artem Bezsmertnyi]: https://github.com/mrdeathless
|
[Artem Bezsmertnyi]: https://github.com/mrdeathless
|
||||||
|
[BuonOmo]: https://github.com/BuonOmo
|
||||||
[Carlos Afonso]: https://github.com/afonsocarlos
|
[Carlos Afonso]: https://github.com/afonsocarlos
|
||||||
[Casper Langemeijer]: https://github.com/langemeijer
|
[Casper Langemeijer]: https://github.com/langemeijer
|
||||||
[Daniel West]: http://github.com/danieljwest
|
[Daniel West]: http://github.com/danieljwest
|
||||||
|
|
65
doc/key_bindings.rst
Normal file
65
doc/key_bindings.rst
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
*************
|
||||||
|
Key Bindings:
|
||||||
|
*************
|
||||||
|
|
||||||
|
Most key bindings are simply inherited from `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/index.html>`_ .
|
||||||
|
|
||||||
|
The following key bindings are special to mycli:
|
||||||
|
|
||||||
|
###
|
||||||
|
F2
|
||||||
|
###
|
||||||
|
|
||||||
|
Enable/Disable SmartCompletion Mode.
|
||||||
|
|
||||||
|
###
|
||||||
|
F3
|
||||||
|
###
|
||||||
|
|
||||||
|
Enable/Disable Multiline Mode.
|
||||||
|
|
||||||
|
###
|
||||||
|
F4
|
||||||
|
###
|
||||||
|
|
||||||
|
Toggle between Vi and Emacs mode.
|
||||||
|
|
||||||
|
###
|
||||||
|
Tab
|
||||||
|
###
|
||||||
|
|
||||||
|
Force autocompletion at cursor.
|
||||||
|
|
||||||
|
#######
|
||||||
|
C-space
|
||||||
|
#######
|
||||||
|
|
||||||
|
Initialize autocompletion at cursor.
|
||||||
|
|
||||||
|
If the autocompletion menu is not showing, display it with the appropriate completions for the context.
|
||||||
|
|
||||||
|
If the menu is showing, select the next completion.
|
||||||
|
|
||||||
|
#########
|
||||||
|
ESC Enter
|
||||||
|
#########
|
||||||
|
|
||||||
|
Introduce a line break in multi-line mode, or dispatch the command in single-line mode.
|
||||||
|
|
||||||
|
The sequence ESC-Enter is often sent by Alt-Enter.
|
||||||
|
|
||||||
|
#################################
|
||||||
|
C-x p (Emacs-mode) or > (Vi-mode)
|
||||||
|
#################################
|
||||||
|
|
||||||
|
Prettify and indent current statement, usually into multiple lines.
|
||||||
|
|
||||||
|
Only accepts buffers containing single SQL statements.
|
||||||
|
|
||||||
|
#################################
|
||||||
|
C-x u (Emacs-mode) or < (Vi-mode)
|
||||||
|
#################################
|
||||||
|
|
||||||
|
Unprettify and dedent current statement, usually into one line.
|
||||||
|
|
||||||
|
Only accepts buffers containing single SQL statements.
|
|
@ -24,10 +24,12 @@ Contributors:
|
||||||
* Artem Bezsmertnyi
|
* Artem Bezsmertnyi
|
||||||
* bitkeen
|
* bitkeen
|
||||||
* bjarnagin
|
* bjarnagin
|
||||||
|
* BuonOmo
|
||||||
* caitinggui
|
* caitinggui
|
||||||
* Carlos Afonso
|
* Carlos Afonso
|
||||||
* Casper Langemeijer
|
* Casper Langemeijer
|
||||||
* chainkite
|
* chainkite
|
||||||
|
* Claude Becker
|
||||||
* Colin Caine
|
* Colin Caine
|
||||||
* cxbig
|
* cxbig
|
||||||
* Daniel Black
|
* Daniel Black
|
||||||
|
@ -38,6 +40,7 @@ Contributors:
|
||||||
* Georgy Frolov
|
* Georgy Frolov
|
||||||
* Heath Naylor
|
* Heath Naylor
|
||||||
* Huachao Mao
|
* Huachao Mao
|
||||||
|
* Ishaan Bhimwal
|
||||||
* Jakub Boukal
|
* Jakub Boukal
|
||||||
* jbruno
|
* jbruno
|
||||||
* Jerome Provensal
|
* Jerome Provensal
|
||||||
|
@ -81,6 +84,7 @@ Contributors:
|
||||||
* xeron
|
* xeron
|
||||||
* Yang Zou
|
* Yang Zou
|
||||||
* Yasuhiro Matsumoto
|
* Yasuhiro Matsumoto
|
||||||
|
* Yuanchun Shang
|
||||||
* Zach DeCook
|
* Zach DeCook
|
||||||
* Zane C. Bowers-Hadley
|
* Zane C. Bowers-Hadley
|
||||||
* zer09
|
* zer09
|
||||||
|
@ -88,6 +92,8 @@ Contributors:
|
||||||
* Zhidong
|
* Zhidong
|
||||||
* Zhongyang Guan
|
* Zhongyang Guan
|
||||||
* Arvind Mishra
|
* Arvind Mishra
|
||||||
|
* Kevin Schmeichel
|
||||||
|
* Mel Dafert
|
||||||
|
|
||||||
Created by:
|
Created by:
|
||||||
-----------
|
-----------
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = '1.25.0'
|
__version__ = '1.26.1'
|
||||||
|
|
|
@ -30,6 +30,11 @@ def create_toolbar_tokens_func(mycli, show_fish_help):
|
||||||
'Vi-mode ({})'.format(_get_vi_mode())
|
'Vi-mode ({})'.format(_get_vi_mode())
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if mycli.toolbar_error_message:
|
||||||
|
result.append(
|
||||||
|
('class:bottom-toolbar', ' ' + mycli.toolbar_error_message))
|
||||||
|
mycli.toolbar_error_message = None
|
||||||
|
|
||||||
if show_fish_help():
|
if show_fish_help():
|
||||||
result.append(
|
result.append(
|
||||||
('class:bottom-toolbar', ' Right-arrow to complete suggestion'))
|
('class:bottom-toolbar', ' Right-arrow to complete suggestion'))
|
||||||
|
|
|
@ -47,7 +47,7 @@ class CompletionRefresher(object):
|
||||||
def _bg_refresh(self, sqlexecute, callbacks, completer_options):
|
def _bg_refresh(self, sqlexecute, callbacks, completer_options):
|
||||||
completer = SQLCompleter(**completer_options)
|
completer = SQLCompleter(**completer_options)
|
||||||
|
|
||||||
# Create a new pgexecute method to popoulate the completions.
|
# Create a new pgexecute method to populate the completions.
|
||||||
e = sqlexecute
|
e = sqlexecute
|
||||||
executor = SQLExecute(e.dbname, e.user, e.password, e.host, e.port,
|
executor = SQLExecute(e.dbname, e.user, e.password, e.host, e.port,
|
||||||
e.socket, e.charset, e.local_infile, e.ssl,
|
e.socket, e.charset, e.local_infile, e.ssl,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from prompt_toolkit.enums import EditingMode
|
from prompt_toolkit.enums import EditingMode
|
||||||
from prompt_toolkit.filters import completion_is_selected
|
from prompt_toolkit.filters import completion_is_selected, emacs_mode, vi_mode
|
||||||
from prompt_toolkit.key_binding import KeyBindings
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
@ -61,6 +61,48 @@ def mycli_bindings(mycli):
|
||||||
else:
|
else:
|
||||||
b.start_completion(select_first=False)
|
b.start_completion(select_first=False)
|
||||||
|
|
||||||
|
@kb.add('>', filter=vi_mode)
|
||||||
|
@kb.add('c-x', 'p', filter=emacs_mode)
|
||||||
|
def _(event):
|
||||||
|
"""
|
||||||
|
Prettify and indent current statement, usually into multiple lines.
|
||||||
|
|
||||||
|
Only accepts buffers containing single SQL statements.
|
||||||
|
"""
|
||||||
|
_logger.debug('Detected <C-x p>/> key.')
|
||||||
|
|
||||||
|
b = event.app.current_buffer
|
||||||
|
cursorpos_relative = b.cursor_position / len(b.text)
|
||||||
|
pretty_text = mycli.handle_prettify_binding(b.text)
|
||||||
|
if len(pretty_text) > 0:
|
||||||
|
b.text = pretty_text
|
||||||
|
cursorpos_abs = int(round(cursorpos_relative * len(b.text)))
|
||||||
|
while 0 < cursorpos_abs < len(b.text) \
|
||||||
|
and b.text[cursorpos_abs] in (' ', '\n'):
|
||||||
|
cursorpos_abs -= 1
|
||||||
|
b.cursor_position = min(cursorpos_abs, len(b.text))
|
||||||
|
|
||||||
|
@kb.add('<', filter=vi_mode)
|
||||||
|
@kb.add('c-x', 'u', filter=emacs_mode)
|
||||||
|
def _(event):
|
||||||
|
"""
|
||||||
|
Unprettify and dedent current statement, usually into one line.
|
||||||
|
|
||||||
|
Only accepts buffers containing single SQL statements.
|
||||||
|
"""
|
||||||
|
_logger.debug('Detected <C-x u>/< key.')
|
||||||
|
|
||||||
|
b = event.app.current_buffer
|
||||||
|
cursorpos_relative = b.cursor_position / len(b.text)
|
||||||
|
unpretty_text = mycli.handle_unprettify_binding(b.text)
|
||||||
|
if len(unpretty_text) > 0:
|
||||||
|
b.text = unpretty_text
|
||||||
|
cursorpos_abs = int(round(cursorpos_relative * len(b.text)))
|
||||||
|
while 0 < cursorpos_abs < len(b.text) \
|
||||||
|
and b.text[cursorpos_abs] in (' ', '\n'):
|
||||||
|
cursorpos_abs -= 1
|
||||||
|
b.cursor_position = min(cursorpos_abs, len(b.text))
|
||||||
|
|
||||||
@kb.add('enter', filter=completion_is_selected)
|
@kb.add('enter', filter=completion_is_selected)
|
||||||
def _(event):
|
def _(event):
|
||||||
"""Makes the enter key work as the tab key only when showing the menu.
|
"""Makes the enter key work as the tab key only when showing the menu.
|
||||||
|
|
|
@ -24,6 +24,7 @@ from cli_helpers.tabular_output import preprocessors
|
||||||
from cli_helpers.utils import strip_ansi
|
from cli_helpers.utils import strip_ansi
|
||||||
import click
|
import click
|
||||||
import sqlparse
|
import sqlparse
|
||||||
|
import sqlglot
|
||||||
from mycli.packages.parseutils import is_dropping_database, is_destructive
|
from mycli.packages.parseutils import is_dropping_database, is_destructive
|
||||||
from prompt_toolkit.completion import DynamicCompleter
|
from prompt_toolkit.completion import DynamicCompleter
|
||||||
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
|
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
|
||||||
|
@ -123,6 +124,7 @@ class MyCli(object):
|
||||||
self.logfile = logfile
|
self.logfile = logfile
|
||||||
self.defaults_suffix = defaults_suffix
|
self.defaults_suffix = defaults_suffix
|
||||||
self.login_path = login_path
|
self.login_path = login_path
|
||||||
|
self.toolbar_error_message = None
|
||||||
|
|
||||||
# self.cnf_files is a class variable that stores the list of mysql
|
# self.cnf_files is a class variable that stores the list of mysql
|
||||||
# config files to read in at launch.
|
# config files to read in at launch.
|
||||||
|
@ -341,6 +343,7 @@ class MyCli(object):
|
||||||
'mysqld': {
|
'mysqld': {
|
||||||
'socket': 'default_socket',
|
'socket': 'default_socket',
|
||||||
'port': 'default_port',
|
'port': 'default_port',
|
||||||
|
'user': 'default_user',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +450,7 @@ class MyCli(object):
|
||||||
if not any(v for v in ssl.values()):
|
if not any(v for v in ssl.values()):
|
||||||
ssl = None
|
ssl = None
|
||||||
|
|
||||||
# if the passwd is not specfied try to set it using the password_file option
|
# if the passwd is not specified try to set it using the password_file option
|
||||||
password_from_file = self.get_password_from_file(password_file)
|
password_from_file = self.get_password_from_file(password_file)
|
||||||
passwd = passwd or password_from_file
|
passwd = passwd or password_from_file
|
||||||
|
|
||||||
|
@ -581,6 +584,34 @@ class MyCli(object):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def handle_prettify_binding(self, text):
|
||||||
|
try:
|
||||||
|
statements = sqlglot.parse(text, read='mysql')
|
||||||
|
except Exception as e:
|
||||||
|
statements = []
|
||||||
|
if len(statements) == 1:
|
||||||
|
pretty_text = statements[0].sql(pretty=True, pad=4, dialect='mysql')
|
||||||
|
else:
|
||||||
|
pretty_text = ''
|
||||||
|
self.toolbar_error_message = 'Prettify failed to parse statement'
|
||||||
|
if len(pretty_text) > 0:
|
||||||
|
pretty_text = pretty_text + ';'
|
||||||
|
return pretty_text
|
||||||
|
|
||||||
|
def handle_unprettify_binding(self, text):
|
||||||
|
try:
|
||||||
|
statements = sqlglot.parse(text, read='mysql')
|
||||||
|
except Exception as e:
|
||||||
|
statements = []
|
||||||
|
if len(statements) == 1:
|
||||||
|
unpretty_text = statements[0].sql(pretty=False, dialect='mysql')
|
||||||
|
else:
|
||||||
|
unpretty_text = ''
|
||||||
|
self.toolbar_error_message = 'Unprettify failed to parse statement'
|
||||||
|
if len(unpretty_text) > 0:
|
||||||
|
unpretty_text = unpretty_text + ';'
|
||||||
|
return unpretty_text
|
||||||
|
|
||||||
def run_cli(self):
|
def run_cli(self):
|
||||||
iterations = 0
|
iterations = 0
|
||||||
sqlexecute = self.sqlexecute
|
sqlexecute = self.sqlexecute
|
||||||
|
@ -723,7 +754,7 @@ class MyCli(object):
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
if self.beep_after_seconds > 0 and t >= self.beep_after_seconds:
|
if self.beep_after_seconds > 0 and t >= self.beep_after_seconds:
|
||||||
self.echo('\a', err=True, nl=False)
|
self.bell()
|
||||||
if special.is_timing_enabled():
|
if special.is_timing_enabled():
|
||||||
self.echo('Time: %0.03fs' % t)
|
self.echo('Time: %0.03fs' % t)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -739,19 +770,23 @@ class MyCli(object):
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
# get last connection id
|
# get last connection id
|
||||||
connection_id_to_kill = sqlexecute.connection_id
|
connection_id_to_kill = sqlexecute.connection_id
|
||||||
logger.debug("connection id to kill: %r", connection_id_to_kill)
|
# some mysql compatible databases may not implemente connection_id()
|
||||||
# Restart connection to the database
|
if connection_id_to_kill > 0:
|
||||||
sqlexecute.connect()
|
logger.debug("connection id to kill: %r", connection_id_to_kill)
|
||||||
try:
|
# Restart connection to the database
|
||||||
for title, cur, headers, status in sqlexecute.run('kill %s' % connection_id_to_kill):
|
sqlexecute.connect()
|
||||||
status_str = str(status).lower()
|
try:
|
||||||
if status_str.find('ok') > -1:
|
for title, cur, headers, status in sqlexecute.run('kill %s' % connection_id_to_kill):
|
||||||
logger.debug("cancelled query, connection id: %r, sql: %r",
|
status_str = str(status).lower()
|
||||||
connection_id_to_kill, text)
|
if status_str.find('ok') > -1:
|
||||||
self.echo("cancelled query", err=True, fg='red')
|
logger.debug("cancelled query, connection id: %r, sql: %r",
|
||||||
except Exception as e:
|
connection_id_to_kill, text)
|
||||||
self.echo('Encountered error while cancelling query: {}'.format(e),
|
self.echo("cancelled query", err=True, fg='red')
|
||||||
err=True, fg='red')
|
except Exception as e:
|
||||||
|
self.echo('Encountered error while cancelling query: {}'.format(e),
|
||||||
|
err=True, fg='red')
|
||||||
|
else:
|
||||||
|
logger.debug("Did not get a connection id, skip cancelling query")
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
self.echo('Not Yet Implemented.', fg="yellow")
|
self.echo('Not Yet Implemented.', fg="yellow")
|
||||||
except OperationalError as e:
|
except OperationalError as e:
|
||||||
|
@ -860,6 +895,11 @@ class MyCli(object):
|
||||||
self.log_output(s)
|
self.log_output(s)
|
||||||
click.secho(s, **kwargs)
|
click.secho(s, **kwargs)
|
||||||
|
|
||||||
|
def bell(self):
|
||||||
|
"""Print a bell on the stderr.
|
||||||
|
"""
|
||||||
|
click.secho('\a', err=True, nl=False)
|
||||||
|
|
||||||
def get_output_margin(self, status=None):
|
def get_output_margin(self, status=None):
|
||||||
"""Get the output margin (number of rows for the prompt, footer and
|
"""Get the output margin (number of rows for the prompt, footer and
|
||||||
timing message."""
|
timing message."""
|
||||||
|
@ -933,8 +973,9 @@ class MyCli(object):
|
||||||
os.environ['LESS'] = '-RXF'
|
os.environ['LESS'] = '-RXF'
|
||||||
|
|
||||||
cnf = self.read_my_cnf_files(self.cnf_files, ['pager', 'skip-pager'])
|
cnf = self.read_my_cnf_files(self.cnf_files, ['pager', 'skip-pager'])
|
||||||
if cnf['pager']:
|
cnf_pager = cnf['pager'] or self.config['main']['pager']
|
||||||
special.set_pager(cnf['pager'])
|
if cnf_pager:
|
||||||
|
special.set_pager(cnf_pager)
|
||||||
self.explicit_pager = True
|
self.explicit_pager = True
|
||||||
else:
|
else:
|
||||||
self.explicit_pager = False
|
self.explicit_pager = False
|
||||||
|
@ -1084,6 +1125,8 @@ class MyCli(object):
|
||||||
@click.option('--ssh-config-path', help='Path to ssh configuration.',
|
@click.option('--ssh-config-path', help='Path to ssh configuration.',
|
||||||
default=os.path.expanduser('~') + '/.ssh/config')
|
default=os.path.expanduser('~') + '/.ssh/config')
|
||||||
@click.option('--ssh-config-host', help='Host to connect to ssh server reading from ssh configuration.')
|
@click.option('--ssh-config-host', help='Host to connect to ssh server reading from ssh configuration.')
|
||||||
|
@click.option('--ssl', 'ssl_enable', is_flag=True,
|
||||||
|
help='Enable SSL for connection (automatically enabled with other flags).')
|
||||||
@click.option('--ssl-ca', help='CA file in PEM format.',
|
@click.option('--ssl-ca', help='CA file in PEM format.',
|
||||||
type=click.Path(exists=True))
|
type=click.Path(exists=True))
|
||||||
@click.option('--ssl-capath', help='CA directory.')
|
@click.option('--ssl-capath', help='CA directory.')
|
||||||
|
@ -1142,7 +1185,7 @@ class MyCli(object):
|
||||||
def cli(database, user, host, port, socket, password, dbname,
|
def cli(database, user, host, port, socket, password, dbname,
|
||||||
version, verbose, prompt, logfile, defaults_group_suffix,
|
version, verbose, prompt, logfile, defaults_group_suffix,
|
||||||
defaults_file, login_path, auto_vertical_output, local_infile,
|
defaults_file, login_path, auto_vertical_output, local_infile,
|
||||||
ssl_ca, ssl_capath, ssl_cert, ssl_key, ssl_cipher,
|
ssl_enable, ssl_ca, ssl_capath, ssl_cert, ssl_key, ssl_cipher,
|
||||||
ssl_verify_server_cert, table, csv, warn, execute, myclirc, dsn,
|
ssl_verify_server_cert, table, csv, warn, execute, myclirc, dsn,
|
||||||
list_dsn, ssh_user, ssh_host, ssh_port, ssh_password,
|
list_dsn, ssh_user, ssh_host, ssh_port, ssh_password,
|
||||||
ssh_key_filename, list_ssh_config, ssh_config_path, ssh_config_host,
|
ssh_key_filename, list_ssh_config, ssh_config_path, ssh_config_host,
|
||||||
|
@ -1197,6 +1240,7 @@ def cli(database, user, host, port, socket, password, dbname,
|
||||||
database = dbname or database
|
database = dbname or database
|
||||||
|
|
||||||
ssl = {
|
ssl = {
|
||||||
|
'enable': ssl_enable,
|
||||||
'ca': ssl_ca and os.path.expanduser(ssl_ca),
|
'ca': ssl_ca and os.path.expanduser(ssl_ca),
|
||||||
'cert': ssl_cert and os.path.expanduser(ssl_cert),
|
'cert': ssl_cert and os.path.expanduser(ssl_cert),
|
||||||
'key': ssl_key and os.path.expanduser(ssl_key),
|
'key': ssl_key and os.path.expanduser(ssl_key),
|
||||||
|
|
|
@ -27,7 +27,7 @@ log_level = INFO
|
||||||
# line below.
|
# line below.
|
||||||
# audit_log = ~/.mycli-audit.log
|
# audit_log = ~/.mycli-audit.log
|
||||||
|
|
||||||
# Timing of sql statments and table rendering.
|
# Timing of sql statements and table rendering.
|
||||||
timing = True
|
timing = True
|
||||||
|
|
||||||
# Beep after long-running queries are completed; 0 to disable.
|
# Beep after long-running queries are completed; 0 to disable.
|
||||||
|
@ -66,7 +66,7 @@ wider_completion_menu = False
|
||||||
# \R - The current time, in 24-hour military time (0-23)
|
# \R - The current time, in 24-hour military time (0-23)
|
||||||
# \r - The current time, standard 12-hour time (1-12)
|
# \r - The current time, standard 12-hour time (1-12)
|
||||||
# \s - Seconds of the current time
|
# \s - Seconds of the current time
|
||||||
# \t - Product type (Percona, MySQL, MariaDB)
|
# \t - Product type (Percona, MySQL, MariaDB, TiDB)
|
||||||
# \A - DSN alias name (from the [alias_dsn] section)
|
# \A - DSN alias name (from the [alias_dsn] section)
|
||||||
# \u - Username
|
# \u - Username
|
||||||
# \x1b[...m - insert ANSI escape sequence
|
# \x1b[...m - insert ANSI escape sequence
|
||||||
|
@ -89,6 +89,9 @@ keyword_casing = auto
|
||||||
# disabled pager on startup
|
# disabled pager on startup
|
||||||
enable_pager = True
|
enable_pager = True
|
||||||
|
|
||||||
|
# Choose a specific pager
|
||||||
|
pager = 'less'
|
||||||
|
|
||||||
# Custom colors for the completion menu, toolbar, etc.
|
# Custom colors for the completion menu, toolbar, etc.
|
||||||
[colors]
|
[colors]
|
||||||
completion-menu.completion.current = 'bg:#ffffff #000000'
|
completion-menu.completion.current = 'bg:#ffffff #000000'
|
||||||
|
|
|
@ -129,6 +129,8 @@ def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier
|
||||||
prev_keyword, text_before_cursor = find_prev_keyword(text_before_cursor)
|
prev_keyword, text_before_cursor = find_prev_keyword(text_before_cursor)
|
||||||
return suggest_based_on_last_token(prev_keyword, text_before_cursor,
|
return suggest_based_on_last_token(prev_keyword, text_before_cursor,
|
||||||
full_text, identifier)
|
full_text, identifier)
|
||||||
|
elif token is None:
|
||||||
|
return [{'type': 'keyword'}]
|
||||||
else:
|
else:
|
||||||
token_v = token.value.lower()
|
token_v = token.value.lower()
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ def extract_table_identifiers(token_stream):
|
||||||
|
|
||||||
# extract_tables is inspired from examples in the sqlparse lib.
|
# extract_tables is inspired from examples in the sqlparse lib.
|
||||||
def extract_tables(sql):
|
def extract_tables(sql):
|
||||||
"""Extract the table names from an SQL statment.
|
"""Extract the table names from an SQL statement.
|
||||||
|
|
||||||
Returns a list of (schema, table, alias) tuples
|
Returns a list of (schema, table, alias) tuples
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ def list_tables(cur, arg=None, arg_type=PARSED_QUERY, verbose=False):
|
||||||
|
|
||||||
return [(None, tables, headers, status)]
|
return [(None, tables, headers, status)]
|
||||||
|
|
||||||
|
|
||||||
@special_command('\\l', '\\l', 'List databases.', arg_type=RAW_QUERY, case_sensitive=True)
|
@special_command('\\l', '\\l', 'List databases.', arg_type=RAW_QUERY, case_sensitive=True)
|
||||||
def list_databases(cur, **_):
|
def list_databases(cur, **_):
|
||||||
query = 'SHOW DATABASES'
|
query = 'SHOW DATABASES'
|
||||||
|
@ -45,6 +46,7 @@ def list_databases(cur, **_):
|
||||||
else:
|
else:
|
||||||
return [(None, None, None, '')]
|
return [(None, None, None, '')]
|
||||||
|
|
||||||
|
|
||||||
@special_command('status', '\\s', 'Get status information from the server.',
|
@special_command('status', '\\s', 'Get status information from the server.',
|
||||||
arg_type=RAW_QUERY, aliases=('\\s', ), case_sensitive=True)
|
arg_type=RAW_QUERY, aliases=('\\s', ), case_sensitive=True)
|
||||||
def status(cur, **_):
|
def status(cur, **_):
|
||||||
|
@ -146,7 +148,8 @@ def status(cur, **_):
|
||||||
stats.append('Queries: {0}'.format(status['Queries']))
|
stats.append('Queries: {0}'.format(status['Queries']))
|
||||||
stats.append('Slow queries: {0}'.format(status['Slow_queries']))
|
stats.append('Slow queries: {0}'.format(status['Slow_queries']))
|
||||||
stats.append('Opens: {0}'.format(status['Opened_tables']))
|
stats.append('Opens: {0}'.format(status['Opened_tables']))
|
||||||
stats.append('Flush tables: {0}'.format(status['Flush_commands']))
|
if 'Flush_commands' in status:
|
||||||
|
stats.append('Flush tables: {0}'.format(status['Flush_commands']))
|
||||||
stats.append('Open tables: {0}'.format(status['Open_tables']))
|
stats.append('Open tables: {0}'.format(status['Open_tables']))
|
||||||
if 'Queries' in status:
|
if 'Queries' in status:
|
||||||
queries_per_second = int(status['Queries']) / int(status['Uptime'])
|
queries_per_second = int(status['Queries']) / int(status['Uptime'])
|
||||||
|
|
|
@ -28,6 +28,7 @@ class ServerSpecies(enum.Enum):
|
||||||
MySQL = 'MySQL'
|
MySQL = 'MySQL'
|
||||||
MariaDB = 'MariaDB'
|
MariaDB = 'MariaDB'
|
||||||
Percona = 'Percona'
|
Percona = 'Percona'
|
||||||
|
TiDB = 'TiDB'
|
||||||
Unknown = 'MySQL'
|
Unknown = 'MySQL'
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ class ServerInfo:
|
||||||
|
|
||||||
re_species = (
|
re_species = (
|
||||||
(r'(?P<version>[0-9\.]+)-MariaDB', ServerSpecies.MariaDB),
|
(r'(?P<version>[0-9\.]+)-MariaDB', ServerSpecies.MariaDB),
|
||||||
|
(r'(?P<version>[0-9\.]+)[a-z0-9]*-TiDB', ServerSpecies.TiDB),
|
||||||
(r'(?P<version>[0-9\.]+)[a-z0-9]*-(?P<comment>[0-9]+$)',
|
(r'(?P<version>[0-9\.]+)[a-z0-9]*-(?P<comment>[0-9]+$)',
|
||||||
ServerSpecies.Percona),
|
ServerSpecies.Percona),
|
||||||
(r'(?P<version>[0-9\.]+)[a-z0-9]*-(?P<comment>[A-Za-z0-9_]+)',
|
(r'(?P<version>[0-9\.]+)[a-z0-9]*-(?P<comment>[A-Za-z0-9_]+)',
|
||||||
|
@ -338,10 +340,16 @@ class SQLExecute(object):
|
||||||
def reset_connection_id(self):
|
def reset_connection_id(self):
|
||||||
# Remember current connection id
|
# Remember current connection id
|
||||||
_logger.debug('Get current connection id')
|
_logger.debug('Get current connection id')
|
||||||
res = self.run('select connection_id()')
|
try:
|
||||||
for title, cur, headers, status in res:
|
res = self.run('select connection_id()')
|
||||||
self.connection_id = cur.fetchone()[0]
|
for title, cur, headers, status in res:
|
||||||
_logger.debug('Current connection id: %s', self.connection_id)
|
self.connection_id = cur.fetchone()[0]
|
||||||
|
except Exception as e:
|
||||||
|
# See #1054
|
||||||
|
self.connection_id = -1
|
||||||
|
_logger.error('Failed to get connection id: %s', e)
|
||||||
|
else:
|
||||||
|
_logger.debug('Current connection id: %s', self.connection_id)
|
||||||
|
|
||||||
def change_db(self, db):
|
def change_db(self, db):
|
||||||
self.conn.select_db(db)
|
self.conn.select_db(db)
|
||||||
|
|
|
@ -10,4 +10,8 @@ autopep8==1.3.3
|
||||||
colorama>=0.4.1
|
colorama>=0.4.1
|
||||||
git+https://github.com/hayd/pep8radius.git # --error-status option not released
|
git+https://github.com/hayd/pep8radius.git # --error-status option not released
|
||||||
click>=7.0
|
click>=7.0
|
||||||
paramiko==2.7.1
|
paramiko==2.11.0
|
||||||
|
pyperclip>=1.8.1
|
||||||
|
importlib_resources>=5.0.0
|
||||||
|
pyaes>=1.6.1
|
||||||
|
sqlglot>=5.1.3
|
||||||
|
|
10
setup.py
10
setup.py
|
@ -18,12 +18,15 @@ description = 'CLI for MySQL Database. With auto-completion and syntax highlight
|
||||||
|
|
||||||
install_requirements = [
|
install_requirements = [
|
||||||
'click >= 7.0',
|
'click >= 7.0',
|
||||||
'cryptography >= 1.0.0',
|
# Temporary to suppress paramiko Blowfish warning which breaks CI.
|
||||||
|
# Pinning cryptography should not be needed after paramiko 2.11.0.
|
||||||
|
'cryptography == 36.0.2',
|
||||||
# 'Pygments>=1.6,<=2.11.1',
|
# 'Pygments>=1.6,<=2.11.1',
|
||||||
'Pygments>=1.6',
|
'Pygments>=1.6',
|
||||||
'prompt_toolkit>=3.0.6,<4.0.0',
|
'prompt_toolkit>=3.0.6,<4.0.0',
|
||||||
'PyMySQL >= 0.9.2',
|
'PyMySQL >= 0.9.2',
|
||||||
'sqlparse>=0.3.0,<0.5.0',
|
'sqlparse>=0.3.0,<0.5.0',
|
||||||
|
'sqlglot>=5.1.3',
|
||||||
'configobj >= 5.0.5',
|
'configobj >= 5.0.5',
|
||||||
'cli_helpers[styles] >= 2.2.1',
|
'cli_helpers[styles] >= 2.2.1',
|
||||||
'pyperclip >= 1.8.1',
|
'pyperclip >= 1.8.1',
|
||||||
|
@ -101,16 +104,17 @@ setup(
|
||||||
'console_scripts': ['mycli = mycli.main:cli'],
|
'console_scripts': ['mycli = mycli.main:cli'],
|
||||||
},
|
},
|
||||||
cmdclass={'lint': lint, 'test': test},
|
cmdclass={'lint': lint, 'test': test},
|
||||||
python_requires=">=3.6",
|
python_requires=">=3.7",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: Unix',
|
'Operating System :: Unix',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.6',
|
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
'Programming Language :: SQL',
|
'Programming Language :: SQL',
|
||||||
'Topic :: Database',
|
'Topic :: Database',
|
||||||
'Topic :: Database :: Front-Ends',
|
'Topic :: Database :: Front-Ends',
|
||||||
|
|
|
@ -6,8 +6,8 @@ import mycli.sqlexecute
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def connection():
|
def connection():
|
||||||
create_db('_test_db')
|
create_db('mycli_test_db')
|
||||||
connection = db_connection('_test_db')
|
connection = db_connection('mycli_test_db')
|
||||||
yield connection
|
yield connection
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
|
@ -22,7 +22,7 @@ def cursor(connection):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def executor(connection):
|
def executor(connection):
|
||||||
return mycli.sqlexecute.SQLExecute(
|
return mycli.sqlexecute.SQLExecute(
|
||||||
database='_test_db', user=USER,
|
database='mycli_test_db', user=USER,
|
||||||
host=HOST, password=PASSWORD, port=PORT, socket=None, charset=CHARSET,
|
host=HOST, password=PASSWORD, port=PORT, socket=None, charset=CHARSET,
|
||||||
local_infile=False, ssl=None, ssh_user=SSH_USER, ssh_host=SSH_HOST,
|
local_infile=False, ssl=None, ssh_user=SSH_USER, ssh_host=SSH_HOST,
|
||||||
ssh_port=SSH_PORT, ssh_password=None, ssh_key_filename=None
|
ssh_port=SSH_PORT, ssh_password=None, ssh_key_filename=None
|
||||||
|
|
|
@ -542,7 +542,14 @@ def test_favorite_name_suggestion(expression):
|
||||||
suggestions = suggest_type(expression, expression)
|
suggestions = suggest_type(expression, expression)
|
||||||
assert suggestions == [{'type': 'favoritequery'}]
|
assert suggestions == [{'type': 'favoritequery'}]
|
||||||
|
|
||||||
|
|
||||||
def test_order_by():
|
def test_order_by():
|
||||||
text = 'select * from foo order by '
|
text = 'select * from foo order by '
|
||||||
suggestions = suggest_type(text, text)
|
suggestions = suggest_type(text, text)
|
||||||
assert suggestions == [{'tables': [(None, 'foo', None)], 'type': 'column'}]
|
assert suggestions == [{'tables': [(None, 'foo', None)], 'type': 'column'}]
|
||||||
|
|
||||||
|
|
||||||
|
def test_quoted_where():
|
||||||
|
text = "'where i=';"
|
||||||
|
suggestions = suggest_type(text, text)
|
||||||
|
assert suggestions == [{'type': 'keyword'}]
|
||||||
|
|
|
@ -25,7 +25,7 @@ os.environ['MYSQL_TEST_LOGIN_FILE'] = login_path_file
|
||||||
CLI_ARGS = ['--user', USER, '--host', HOST, '--port', PORT,
|
CLI_ARGS = ['--user', USER, '--host', HOST, '--port', PORT,
|
||||||
'--password', PASSWORD, '--myclirc', default_config_file,
|
'--password', PASSWORD, '--myclirc', default_config_file,
|
||||||
'--defaults-file', default_config_file,
|
'--defaults-file', default_config_file,
|
||||||
'_test_db']
|
'mycli_test_db']
|
||||||
|
|
||||||
|
|
||||||
@dbtest
|
@dbtest
|
||||||
|
@ -283,6 +283,20 @@ def test_list_dsn():
|
||||||
assert result.output == "test : mysql://test/test\n"
|
assert result.output == "test : mysql://test/test\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_prettify_statement():
|
||||||
|
statement = 'SELECT 1'
|
||||||
|
m = MyCli()
|
||||||
|
pretty_statement = m.handle_prettify_binding(statement)
|
||||||
|
assert pretty_statement == 'SELECT\n 1;'
|
||||||
|
|
||||||
|
|
||||||
|
def test_unprettify_statement():
|
||||||
|
statement = 'SELECT\n 1'
|
||||||
|
m = MyCli()
|
||||||
|
unpretty_statement = m.handle_unprettify_binding(statement)
|
||||||
|
assert unpretty_statement == 'SELECT 1;'
|
||||||
|
|
||||||
|
|
||||||
def test_list_ssh_config():
|
def test_list_ssh_config():
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
with NamedTemporaryFile(mode="w") as ssh_config:
|
with NamedTemporaryFile(mode="w") as ssh_config:
|
||||||
|
@ -305,19 +319,25 @@ def test_dsn(monkeypatch):
|
||||||
# Setup classes to mock mycli.main.MyCli
|
# Setup classes to mock mycli.main.MyCli
|
||||||
class Formatter:
|
class Formatter:
|
||||||
format_name = None
|
format_name = None
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
def debug(self, *args, **args_dict):
|
def debug(self, *args, **args_dict):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def warning(self, *args, **args_dict):
|
def warning(self, *args, **args_dict):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class MockMyCli:
|
class MockMyCli:
|
||||||
config = {'alias_dsn': {}}
|
config = {'alias_dsn': {}}
|
||||||
|
|
||||||
def __init__(self, **args):
|
def __init__(self, **args):
|
||||||
self.logger = Logger()
|
self.logger = Logger()
|
||||||
self.destructive_warning = False
|
self.destructive_warning = False
|
||||||
self.formatter = Formatter()
|
self.formatter = Formatter()
|
||||||
|
|
||||||
def connect(self, **args):
|
def connect(self, **args):
|
||||||
MockMyCli.connect_args = args
|
MockMyCli.connect_args = args
|
||||||
|
|
||||||
def run_query(self, query, new_line=True):
|
def run_query(self, query, new_line=True):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ def test_table_and_columns_query(executor):
|
||||||
@dbtest
|
@dbtest
|
||||||
def test_database_list(executor):
|
def test_database_list(executor):
|
||||||
databases = executor.databases()
|
databases = executor.databases()
|
||||||
assert '_test_db' in databases
|
assert 'mycli_test_db' in databases
|
||||||
|
|
||||||
|
|
||||||
@dbtest
|
@dbtest
|
||||||
|
@ -276,6 +276,7 @@ def test_multiple_results(executor):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'version_string, species, parsed_version_string, version',
|
'version_string, species, parsed_version_string, version',
|
||||||
(
|
(
|
||||||
|
('5.7.25-TiDB-v6.1.0','TiDB', '5.7.25', 50725),
|
||||||
('5.7.32-35', 'Percona', '5.7.32', 50732),
|
('5.7.32-35', 'Percona', '5.7.32', 50732),
|
||||||
('5.7.32-0ubuntu0.18.04.1', 'MySQL', '5.7.32', 50732),
|
('5.7.32-0ubuntu0.18.04.1', 'MySQL', '5.7.32', 50732),
|
||||||
('10.5.8-MariaDB-1:10.5.8+maria~focal', 'MariaDB', '10.5.8', 100508),
|
('10.5.8-MariaDB-1:10.5.8+maria~focal', 'MariaDB', '10.5.8', 100508),
|
||||||
|
|
|
@ -41,8 +41,8 @@ dbtest = pytest.mark.skipif(
|
||||||
def create_db(dbname):
|
def create_db(dbname):
|
||||||
with db_connection().cursor() as cur:
|
with db_connection().cursor() as cur:
|
||||||
try:
|
try:
|
||||||
cur.execute('''DROP DATABASE IF EXISTS _test_db''')
|
cur.execute('''DROP DATABASE IF EXISTS mycli_test_db''')
|
||||||
cur.execute('''CREATE DATABASE _test_db''')
|
cur.execute('''CREATE DATABASE mycli_test_db''')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue