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
|
@ -24,10 +24,12 @@ Contributors:
|
|||
* Artem Bezsmertnyi
|
||||
* bitkeen
|
||||
* bjarnagin
|
||||
* BuonOmo
|
||||
* caitinggui
|
||||
* Carlos Afonso
|
||||
* Casper Langemeijer
|
||||
* chainkite
|
||||
* Claude Becker
|
||||
* Colin Caine
|
||||
* cxbig
|
||||
* Daniel Black
|
||||
|
@ -38,6 +40,7 @@ Contributors:
|
|||
* Georgy Frolov
|
||||
* Heath Naylor
|
||||
* Huachao Mao
|
||||
* Ishaan Bhimwal
|
||||
* Jakub Boukal
|
||||
* jbruno
|
||||
* Jerome Provensal
|
||||
|
@ -81,6 +84,7 @@ Contributors:
|
|||
* xeron
|
||||
* Yang Zou
|
||||
* Yasuhiro Matsumoto
|
||||
* Yuanchun Shang
|
||||
* Zach DeCook
|
||||
* Zane C. Bowers-Hadley
|
||||
* zer09
|
||||
|
@ -88,6 +92,8 @@ Contributors:
|
|||
* Zhidong
|
||||
* Zhongyang Guan
|
||||
* Arvind Mishra
|
||||
* Kevin Schmeichel
|
||||
* Mel Dafert
|
||||
|
||||
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())
|
||||
))
|
||||
|
||||
if mycli.toolbar_error_message:
|
||||
result.append(
|
||||
('class:bottom-toolbar', ' ' + mycli.toolbar_error_message))
|
||||
mycli.toolbar_error_message = None
|
||||
|
||||
if show_fish_help():
|
||||
result.append(
|
||||
('class:bottom-toolbar', ' Right-arrow to complete suggestion'))
|
||||
|
|
|
@ -47,7 +47,7 @@ class CompletionRefresher(object):
|
|||
def _bg_refresh(self, sqlexecute, callbacks, 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
|
||||
executor = SQLExecute(e.dbname, e.user, e.password, e.host, e.port,
|
||||
e.socket, e.charset, e.local_infile, e.ssl,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
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
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -61,6 +61,48 @@ def mycli_bindings(mycli):
|
|||
else:
|
||||
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)
|
||||
def _(event):
|
||||
"""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
|
||||
import click
|
||||
import sqlparse
|
||||
import sqlglot
|
||||
from mycli.packages.parseutils import is_dropping_database, is_destructive
|
||||
from prompt_toolkit.completion import DynamicCompleter
|
||||
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
|
||||
|
@ -123,6 +124,7 @@ class MyCli(object):
|
|||
self.logfile = logfile
|
||||
self.defaults_suffix = defaults_suffix
|
||||
self.login_path = login_path
|
||||
self.toolbar_error_message = None
|
||||
|
||||
# self.cnf_files is a class variable that stores the list of mysql
|
||||
# config files to read in at launch.
|
||||
|
@ -341,6 +343,7 @@ class MyCli(object):
|
|||
'mysqld': {
|
||||
'socket': 'default_socket',
|
||||
'port': 'default_port',
|
||||
'user': 'default_user',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -447,7 +450,7 @@ class MyCli(object):
|
|||
if not any(v for v in ssl.values()):
|
||||
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)
|
||||
passwd = passwd or password_from_file
|
||||
|
||||
|
@ -581,6 +584,34 @@ class MyCli(object):
|
|||
return True
|
||||
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):
|
||||
iterations = 0
|
||||
sqlexecute = self.sqlexecute
|
||||
|
@ -723,7 +754,7 @@ class MyCli(object):
|
|||
except KeyboardInterrupt:
|
||||
pass
|
||||
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():
|
||||
self.echo('Time: %0.03fs' % t)
|
||||
except KeyboardInterrupt:
|
||||
|
@ -739,19 +770,23 @@ class MyCli(object):
|
|||
except KeyboardInterrupt:
|
||||
# get last connection id
|
||||
connection_id_to_kill = sqlexecute.connection_id
|
||||
logger.debug("connection id to kill: %r", connection_id_to_kill)
|
||||
# Restart connection to the database
|
||||
sqlexecute.connect()
|
||||
try:
|
||||
for title, cur, headers, status in sqlexecute.run('kill %s' % connection_id_to_kill):
|
||||
status_str = str(status).lower()
|
||||
if status_str.find('ok') > -1:
|
||||
logger.debug("cancelled query, connection id: %r, sql: %r",
|
||||
connection_id_to_kill, text)
|
||||
self.echo("cancelled query", err=True, fg='red')
|
||||
except Exception as e:
|
||||
self.echo('Encountered error while cancelling query: {}'.format(e),
|
||||
err=True, fg='red')
|
||||
# some mysql compatible databases may not implemente connection_id()
|
||||
if connection_id_to_kill > 0:
|
||||
logger.debug("connection id to kill: %r", connection_id_to_kill)
|
||||
# Restart connection to the database
|
||||
sqlexecute.connect()
|
||||
try:
|
||||
for title, cur, headers, status in sqlexecute.run('kill %s' % connection_id_to_kill):
|
||||
status_str = str(status).lower()
|
||||
if status_str.find('ok') > -1:
|
||||
logger.debug("cancelled query, connection id: %r, sql: %r",
|
||||
connection_id_to_kill, text)
|
||||
self.echo("cancelled query", 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:
|
||||
self.echo('Not Yet Implemented.', fg="yellow")
|
||||
except OperationalError as e:
|
||||
|
@ -860,6 +895,11 @@ class MyCli(object):
|
|||
self.log_output(s)
|
||||
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):
|
||||
"""Get the output margin (number of rows for the prompt, footer and
|
||||
timing message."""
|
||||
|
@ -933,8 +973,9 @@ class MyCli(object):
|
|||
os.environ['LESS'] = '-RXF'
|
||||
|
||||
cnf = self.read_my_cnf_files(self.cnf_files, ['pager', 'skip-pager'])
|
||||
if cnf['pager']:
|
||||
special.set_pager(cnf['pager'])
|
||||
cnf_pager = cnf['pager'] or self.config['main']['pager']
|
||||
if cnf_pager:
|
||||
special.set_pager(cnf_pager)
|
||||
self.explicit_pager = True
|
||||
else:
|
||||
self.explicit_pager = False
|
||||
|
@ -1084,6 +1125,8 @@ class MyCli(object):
|
|||
@click.option('--ssh-config-path', help='Path to ssh configuration.',
|
||||
default=os.path.expanduser('~') + '/.ssh/config')
|
||||
@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.',
|
||||
type=click.Path(exists=True))
|
||||
@click.option('--ssl-capath', help='CA directory.')
|
||||
|
@ -1142,7 +1185,7 @@ class MyCli(object):
|
|||
def cli(database, user, host, port, socket, password, dbname,
|
||||
version, verbose, prompt, logfile, defaults_group_suffix,
|
||||
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,
|
||||
list_dsn, ssh_user, ssh_host, ssh_port, ssh_password,
|
||||
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
|
||||
|
||||
ssl = {
|
||||
'enable': ssl_enable,
|
||||
'ca': ssl_ca and os.path.expanduser(ssl_ca),
|
||||
'cert': ssl_cert and os.path.expanduser(ssl_cert),
|
||||
'key': ssl_key and os.path.expanduser(ssl_key),
|
||||
|
|
|
@ -27,7 +27,7 @@ log_level = INFO
|
|||
# line below.
|
||||
# audit_log = ~/.mycli-audit.log
|
||||
|
||||
# Timing of sql statments and table rendering.
|
||||
# Timing of sql statements and table rendering.
|
||||
timing = True
|
||||
|
||||
# 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, standard 12-hour time (1-12)
|
||||
# \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)
|
||||
# \u - Username
|
||||
# \x1b[...m - insert ANSI escape sequence
|
||||
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
return suggest_based_on_last_token(prev_keyword, text_before_cursor,
|
||||
full_text, identifier)
|
||||
elif token is None:
|
||||
return [{'type': 'keyword'}]
|
||||
else:
|
||||
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.
|
||||
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
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ def list_tables(cur, arg=None, arg_type=PARSED_QUERY, verbose=False):
|
|||
|
||||
return [(None, tables, headers, status)]
|
||||
|
||||
|
||||
@special_command('\\l', '\\l', 'List databases.', arg_type=RAW_QUERY, case_sensitive=True)
|
||||
def list_databases(cur, **_):
|
||||
query = 'SHOW DATABASES'
|
||||
|
@ -45,6 +46,7 @@ def list_databases(cur, **_):
|
|||
else:
|
||||
return [(None, None, None, '')]
|
||||
|
||||
|
||||
@special_command('status', '\\s', 'Get status information from the server.',
|
||||
arg_type=RAW_QUERY, aliases=('\\s', ), case_sensitive=True)
|
||||
def status(cur, **_):
|
||||
|
@ -146,7 +148,8 @@ def status(cur, **_):
|
|||
stats.append('Queries: {0}'.format(status['Queries']))
|
||||
stats.append('Slow queries: {0}'.format(status['Slow_queries']))
|
||||
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']))
|
||||
if 'Queries' in status:
|
||||
queries_per_second = int(status['Queries']) / int(status['Uptime'])
|
||||
|
|
|
@ -28,6 +28,7 @@ class ServerSpecies(enum.Enum):
|
|||
MySQL = 'MySQL'
|
||||
MariaDB = 'MariaDB'
|
||||
Percona = 'Percona'
|
||||
TiDB = 'TiDB'
|
||||
Unknown = 'MySQL'
|
||||
|
||||
|
||||
|
@ -55,6 +56,7 @@ class ServerInfo:
|
|||
|
||||
re_species = (
|
||||
(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]+$)',
|
||||
ServerSpecies.Percona),
|
||||
(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):
|
||||
# Remember current connection id
|
||||
_logger.debug('Get current connection id')
|
||||
res = self.run('select connection_id()')
|
||||
for title, cur, headers, status in res:
|
||||
self.connection_id = cur.fetchone()[0]
|
||||
_logger.debug('Current connection id: %s', self.connection_id)
|
||||
try:
|
||||
res = self.run('select connection_id()')
|
||||
for title, cur, headers, status in res:
|
||||
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):
|
||||
self.conn.select_db(db)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue