summaryrefslogtreecommitdiffstats
path: root/mycli
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-02-08 11:28:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-02-08 11:28:55 +0000
commit12ffec7e2e719776343027f9fbdd0d070d3ff68c (patch)
tree6eeb1a166346e9f319fd3056974c5783d3428506 /mycli
parentAdding debian version 1.22.2-0.1. (diff)
downloadmycli-12ffec7e2e719776343027f9fbdd0d070d3ff68c.tar.xz
mycli-12ffec7e2e719776343027f9fbdd0d070d3ff68c.zip
Merging upstream version 1.23.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mycli')
-rw-r--r--mycli/AUTHORS10
-rw-r--r--mycli/__init__.py2
-rw-r--r--mycli/clibuffer.py2
-rw-r--r--mycli/clistyle.py34
-rw-r--r--mycli/clitoolbar.py1
-rw-r--r--mycli/magic.py2
-rwxr-xr-xmycli/main.py90
-rw-r--r--mycli/myclirc32
-rw-r--r--mycli/packages/completion_engine.py3
-rw-r--r--mycli/packages/parseutils.py4
-rw-r--r--mycli/packages/special/iocommands.py118
-rw-r--r--mycli/packages/special/main.py2
-rw-r--r--mycli/sqlcompleter.py2
-rw-r--r--mycli/sqlexecute.py21
14 files changed, 273 insertions, 50 deletions
diff --git a/mycli/AUTHORS b/mycli/AUTHORS
index b3636d9..221ce8b 100644
--- a/mycli/AUTHORS
+++ b/mycli/AUTHORS
@@ -72,6 +72,16 @@ Contributors:
* Jakub Boukal
* Takeshi D. Itoh
* laixintao
+ * Zach DeCook
+ * kevinhwang91
+ * KITAGAWA Yasutaka
+ * bitkeen
+ * Morgan Mitchell
+ * Massimiliano Torromeo
+ * Roland Walker
+ * xeron
+ * 0xflotus
+ * Seamile
Creator:
--------
diff --git a/mycli/__init__.py b/mycli/__init__.py
index 53bfe2e..375471f 100644
--- a/mycli/__init__.py
+++ b/mycli/__init__.py
@@ -1 +1 @@
-__version__ = '1.22.2'
+__version__ = '1.23.2'
diff --git a/mycli/clibuffer.py b/mycli/clibuffer.py
index c9d29d1..c0cb5c1 100644
--- a/mycli/clibuffer.py
+++ b/mycli/clibuffer.py
@@ -39,6 +39,8 @@ def _multiline_exception(text):
text.endswith('\\g') or
text.endswith('\\G') or
+ text.endswith(r'\e') or
+ text.endswith(r'\clip') or
# Exit doesn't need semi-column`
(text == 'exit') or
diff --git a/mycli/clistyle.py b/mycli/clistyle.py
index c94f793..b0ac992 100644
--- a/mycli/clistyle.py
+++ b/mycli/clistyle.py
@@ -34,6 +34,7 @@ TOKEN_TO_PROMPT_STYLE = {
Token.Output.Header: 'output.header',
Token.Output.OddRow: 'output.odd-row',
Token.Output.EvenRow: 'output.even-row',
+ Token.Output.Null: 'output.null',
Token.Prompt: 'prompt',
Token.Continuation: 'continuation',
}
@@ -43,6 +44,36 @@ PROMPT_STYLE_TO_TOKEN = {
v: k for k, v in TOKEN_TO_PROMPT_STYLE.items()
}
+# all tokens that the Pygments MySQL lexer can produce
+OVERRIDE_STYLE_TO_TOKEN = {
+ 'sql.comment': Token.Comment,
+ 'sql.comment.multi-line': Token.Comment.Multiline,
+ 'sql.comment.single-line': Token.Comment.Single,
+ 'sql.comment.optimizer-hint': Token.Comment.Special,
+ 'sql.escape': Token.Error,
+ 'sql.keyword': Token.Keyword,
+ 'sql.datatype': Token.Keyword.Type,
+ 'sql.literal': Token.Literal,
+ 'sql.literal.date': Token.Literal.Date,
+ 'sql.symbol': Token.Name,
+ 'sql.quoted-schema-object': Token.Name.Quoted,
+ 'sql.quoted-schema-object.escape': Token.Name.Quoted.Escape,
+ 'sql.constant': Token.Name.Constant,
+ 'sql.function': Token.Name.Function,
+ 'sql.variable': Token.Name.Variable,
+ 'sql.number': Token.Number,
+ 'sql.number.binary': Token.Number.Bin,
+ 'sql.number.float': Token.Number.Float,
+ 'sql.number.hex': Token.Number.Hex,
+ 'sql.number.integer': Token.Number.Integer,
+ 'sql.operator': Token.Operator,
+ 'sql.punctuation': Token.Punctuation,
+ 'sql.string': Token.String,
+ 'sql.string.double-quouted': Token.String.Double,
+ 'sql.string.escape': Token.String.Escape,
+ 'sql.string.single-quoted': Token.String.Single,
+ 'sql.whitespace': Token.Text,
+}
def parse_pygments_style(token_name, style_object, style_dict):
"""Parse token type and style string.
@@ -107,6 +138,9 @@ def style_factory_output(name, cli_style):
elif token in PROMPT_STYLE_TO_TOKEN:
token_type = PROMPT_STYLE_TO_TOKEN[token]
style.update({token_type: cli_style[token]})
+ elif token in OVERRIDE_STYLE_TO_TOKEN:
+ token_type = OVERRIDE_STYLE_TO_TOKEN[token]
+ style.update({token_type: cli_style[token]})
else:
# TODO: cli helpers will have to switch to ptk.Style
logger.error('Unhandled style / class name: %s', token)
diff --git a/mycli/clitoolbar.py b/mycli/clitoolbar.py
index e03e182..eec2978 100644
--- a/mycli/clitoolbar.py
+++ b/mycli/clitoolbar.py
@@ -48,5 +48,6 @@ def _get_vi_mode():
InputMode.INSERT: 'I',
InputMode.NAVIGATION: 'N',
InputMode.REPLACE: 'R',
+ InputMode.REPLACE_SINGLE: 'R',
InputMode.INSERT_MULTIPLE: 'M',
}[get_app().vi_state.input_mode]
diff --git a/mycli/magic.py b/mycli/magic.py
index 5527f72..b1a3268 100644
--- a/mycli/magic.py
+++ b/mycli/magic.py
@@ -19,7 +19,7 @@ def load_ipython_extension(ipython):
def mycli_line_magic(line):
_logger.debug('mycli magic called: %r', line)
parsed = sql.parse.parse(line, {})
- conn = sql.connection.Connection.get(parsed['connection'])
+ conn = sql.connection.Connection(parsed['connection'])
try:
# A corresponding mycli object already exists
diff --git a/mycli/main.py b/mycli/main.py
index 03797a0..f2b2fd8 100755
--- a/mycli/main.py
+++ b/mycli/main.py
@@ -21,13 +21,14 @@ from cli_helpers.tabular_output import preprocessors
from cli_helpers.utils import strip_ansi
import click
import sqlparse
-from mycli.packages.parseutils import is_dropping_database
+from mycli.packages.parseutils import is_dropping_database, is_destructive
from prompt_toolkit.completion import DynamicCompleter
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
from prompt_toolkit.key_binding.bindings.named_commands import register as prompt_register
from prompt_toolkit.shortcuts import PromptSession, CompleteStyle
from prompt_toolkit.document import Document
from prompt_toolkit.filters import HasFocus, IsDone
+from prompt_toolkit.formatted_text import ANSI
from prompt_toolkit.layout.processors import (HighlightMatchingBracketProcessor,
ConditionalProcessor)
from prompt_toolkit.lexers import PygmentsLexer
@@ -98,7 +99,7 @@ class MyCli(object):
xdg_config_home = "~/.config"
system_config_files = [
'/etc/myclirc',
- os.path.join(xdg_config_home, "mycli", "myclirc")
+ os.path.join(os.path.expanduser(xdg_config_home), "mycli", "myclirc")
]
default_config_file = os.path.join(PACKAGE_ROOT, 'myclirc')
@@ -152,7 +153,7 @@ class MyCli(object):
c['main'].as_bool('auto_vertical_output')
# Write user config if system config wasn't the last config loaded.
- if c.filename not in self.system_config_files:
+ if c.filename not in self.system_config_files and not os.path.exists(myclirc):
write_default_config(self.default_config_file, myclirc)
# audit log
@@ -238,6 +239,9 @@ class MyCli(object):
)
return
+ if arg.startswith('`') and arg.endswith('`'):
+ arg = re.sub(r'^`(.*)`$', r'\1', arg)
+ arg = re.sub(r'``', r'`', arg)
self.sqlexecute.change_db(arg)
yield (None, None, None, 'You are now connected to database "%s" as '
@@ -363,7 +367,7 @@ class MyCli(object):
def connect(self, database='', user='', passwd='', host='', port='',
socket='', charset='', local_infile='', ssl='',
ssh_user='', ssh_host='', ssh_port='',
- ssh_password='', ssh_key_filename=''):
+ ssh_password='', ssh_key_filename='', init_command=''):
cnf = {'database': None,
'user': None,
@@ -387,16 +391,16 @@ class MyCli(object):
database = database or cnf['database']
# Socket interface not supported for SSH connections
- if port or host or ssh_host or ssh_port:
+ if port or (host and host != 'localhost') or (ssh_host and ssh_port):
socket = ''
else:
socket = socket or cnf['socket'] or guess_socket_location()
user = user or cnf['user'] or os.getenv('USER')
host = host or cnf['host']
- port = port or cnf['port']
+ port = int(port or cnf['port'] or 3306)
ssl = ssl or {}
- passwd = passwd or cnf['password']
+ passwd = passwd if isinstance(passwd, str) else cnf['password']
charset = charset or cnf['default-character-set'] or 'utf8'
# Favor whichever local_infile option is set.
@@ -420,7 +424,7 @@ class MyCli(object):
self.sqlexecute = SQLExecute(
database, user, passwd, host, port, socket, charset,
local_infile, ssl, ssh_user, ssh_host, ssh_port,
- ssh_password, ssh_key_filename
+ ssh_password, ssh_key_filename, init_command
)
except OperationalError as e:
if ('Access denied for user' in e.args[1]):
@@ -429,7 +433,7 @@ class MyCli(object):
self.sqlexecute = SQLExecute(
database, user, new_passwd, host, port, socket,
charset, local_infile, ssl, ssh_user, ssh_host,
- ssh_port, ssh_password, ssh_key_filename
+ ssh_port, ssh_password, ssh_key_filename, init_command
)
else:
raise e
@@ -438,7 +442,7 @@ class MyCli(object):
if not WIN and socket:
socket_owner = getpwuid(os.stat(socket).st_uid).pw_name
self.echo(
- f"Connecting to socket {socket}, owned by user {socket_owner}")
+ f"Connecting to socket {socket}, owned by user {socket_owner}", err=True)
try:
_connect()
except OperationalError as e:
@@ -481,7 +485,7 @@ class MyCli(object):
exit(1)
def handle_editor_command(self, text):
- """Editor command is any query that is prefixed or suffixed by a '\e'.
+ r"""Editor command is any query that is prefixed or suffixed by a '\e'.
The reason for a while loop is because a user might edit a query
multiple times. For eg:
@@ -511,6 +515,24 @@ class MyCli(object):
continue
return text
+ def handle_clip_command(self, text):
+ r"""A clip command is any query that is prefixed or suffixed by a
+ '\clip'.
+
+ :param text: Document
+ :return: Boolean
+
+ """
+
+ if special.clip_command(text):
+ query = (special.get_clip_query(text) or
+ self.get_last_query())
+ message = special.copy_query_to_clipboard(sql=query)
+ if message:
+ raise RuntimeError(message)
+ return True
+ return False
+
def run_cli(self):
iterations = 0
sqlexecute = self.sqlexecute
@@ -548,10 +570,13 @@ class MyCli(object):
prompt = self.get_prompt(self.prompt_format)
if self.prompt_format == self.default_prompt and len(prompt) > self.max_len_prompt:
prompt = self.get_prompt('\\d> ')
- return [('class:prompt', prompt)]
+ prompt = prompt.replace("\\x1b", "\x1b")
+ return ANSI(prompt)
def get_continuation(width, *_):
- if self.multiline_continuation_char:
+ if self.multiline_continuation_char == '':
+ continuation = ''
+ elif self.multiline_continuation_char:
left_padding = width - len(self.multiline_continuation_char)
continuation = " " * \
max((left_padding - 1), 0) + \
@@ -580,6 +605,15 @@ class MyCli(object):
self.echo(str(e), err=True, fg='red')
return
+ try:
+ if self.handle_clip_command(text):
+ return
+ except RuntimeError as e:
+ logger.error("sql: %r, error: %r", text, e)
+ logger.error("traceback: %r", traceback.format_exc())
+ self.echo(str(e), err=True, fg='red')
+ return
+
if not text.strip():
return
@@ -654,6 +688,7 @@ class MyCli(object):
result_count += 1
mutating = mutating or destroy or is_mutating(status)
special.unset_once_if_written()
+ special.unset_pipe_once_if_written()
except EOFError as e:
raise e
except KeyboardInterrupt:
@@ -814,6 +849,7 @@ class MyCli(object):
self.log_output(line)
special.write_tee(line)
special.write_once(line)
+ special.write_pipe_once(line)
if fits or output_via_pager:
# buffering
@@ -1051,6 +1087,10 @@ class MyCli(object):
help='Read this path from the login file.')
@click.option('-e', '--execute', type=str,
help='Execute command and quit.')
+@click.option('--init-command', type=str,
+ help='SQL statement to execute after connecting.')
+@click.option('--charset', type=str,
+ help='Character set for MySQL session.')
@click.argument('database', default='', nargs=1)
def cli(database, user, host, port, socket, password, dbname,
version, verbose, prompt, logfile, defaults_group_suffix,
@@ -1058,7 +1098,8 @@ def cli(database, user, host, port, socket, password, dbname,
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):
+ ssh_key_filename, list_ssh_config, ssh_config_path, ssh_config_host,
+ init_command, charset):
"""A MySQL terminal client with auto-completion and syntax highlighting.
\b
@@ -1182,7 +1223,9 @@ def cli(database, user, host, port, socket, password, dbname,
ssh_host=ssh_host,
ssh_port=ssh_port,
ssh_password=ssh_password,
- ssh_key_filename=ssh_key_filename
+ ssh_key_filename=ssh_key_filename,
+ init_command=init_command,
+ charset=charset
)
mycli.logger.debug('Launch Params: \n'
@@ -1217,14 +1260,15 @@ def cli(database, user, host, port, socket, password, dbname,
click.secho('Sorry... :(', err=True, fg='red')
exit(1)
- try:
- sys.stdin = open('/dev/tty')
- except (IOError, OSError):
- mycli.logger.warning('Unable to open TTY as stdin.')
+ if mycli.destructive_warning and is_destructive(stdin_text):
+ try:
+ sys.stdin = open('/dev/tty')
+ warn_confirmed = confirm_destructive_query(stdin_text)
+ except (IOError, OSError):
+ mycli.logger.warning('Unable to open TTY as stdin.')
+ if not warn_confirmed:
+ exit(0)
- if (mycli.destructive_warning and
- confirm_destructive_query(stdin_text) is False):
- exit(0)
try:
new_line = True
@@ -1287,7 +1331,7 @@ def is_select(status):
def thanks_picker(files=()):
contents = []
for line in fileinput.input(files=files):
- m = re.match('^ *\* (.*)', line)
+ m = re.match(r'^ *\* (.*)', line)
if m:
contents.append(m.group(1))
return choice(contents)
diff --git a/mycli/myclirc b/mycli/myclirc
index 534b201..0bde200 100644
--- a/mycli/myclirc
+++ b/mycli/myclirc
@@ -41,6 +41,7 @@ table_format = ascii
# friendly, monokai, paraiso, colorful, murphy, bw, pastie, paraiso, trac, default,
# fruity.
# Screenshots at http://mycli.net/syntax
+# Can be further modified in [colors]
syntax_style = default
# Keybindings: Possible values: emacs, vi.
@@ -65,6 +66,7 @@ wider_completion_menu = False
# \t - Product type (Percona, MySQL, MariaDB)
# \A - DSN alias name (from the [alias_dsn] section)
# \u - Username
+# \x1b[...m - insert ANSI escape sequence
prompt = '\t \u@\h:\d> '
prompt_continuation = '->'
@@ -111,6 +113,36 @@ bottom-toolbar.transaction.failed = 'bg:#222222 #ff005f bold'
output.header = "#00ff5f bold"
output.odd-row = ""
output.even-row = ""
+output.null = "#808080"
+
+# SQL syntax highlighting overrides
+# sql.comment = 'italic #408080'
+# sql.comment.multi-line = ''
+# sql.comment.single-line = ''
+# sql.comment.optimizer-hint = ''
+# sql.escape = 'border:#FF0000'
+# sql.keyword = 'bold #008000'
+# sql.datatype = 'nobold #B00040'
+# sql.literal = ''
+# sql.literal.date = ''
+# sql.symbol = ''
+# sql.quoted-schema-object = ''
+# sql.quoted-schema-object.escape = ''
+# sql.constant = '#880000'
+# sql.function = '#0000FF'
+# sql.variable = '#19177C'
+# sql.number = '#666666'
+# sql.number.binary = ''
+# sql.number.float = ''
+# sql.number.hex = ''
+# sql.number.integer = ''
+# sql.operator = '#666666'
+# sql.punctuation = ''
+# sql.string = '#BA2121'
+# sql.string.double-quouted = ''
+# sql.string.escape = 'bold #BB6622'
+# sql.string.single-quoted = ''
+# sql.whitespace = ''
# Favorite queries.
[favorite_queries]
diff --git a/mycli/packages/completion_engine.py b/mycli/packages/completion_engine.py
index 2b19c32..3cff2cc 100644
--- a/mycli/packages/completion_engine.py
+++ b/mycli/packages/completion_engine.py
@@ -2,7 +2,6 @@ import os
import sys
import sqlparse
from sqlparse.sql import Comparison, Identifier, Where
-from sqlparse.compat import text_type
from .parseutils import last_word, extract_tables, find_prev_keyword
from .special import parse_special_command
@@ -55,7 +54,7 @@ def suggest_type(full_text, text_before_cursor):
stmt_start, stmt_end = 0, 0
for statement in parsed:
- stmt_len = len(text_type(statement))
+ stmt_len = len(str(statement))
stmt_start, stmt_end = stmt_end, stmt_end + stmt_len
if stmt_end >= current_pos:
diff --git a/mycli/packages/parseutils.py b/mycli/packages/parseutils.py
index e3b383e..268e04e 100644
--- a/mycli/packages/parseutils.py
+++ b/mycli/packages/parseutils.py
@@ -11,11 +11,11 @@ cleanup_regex = {
# This matches everything except spaces, parens, colon, comma, and period
'most_punctuations': re.compile(r'([^\.():,\s]+)$'),
# This matches everything except a space.
- 'all_punctuations': re.compile('([^\s]+)$'),
+ 'all_punctuations': re.compile(r'([^\s]+)$'),
}
def last_word(text, include='alphanum_underscore'):
- """
+ r"""
Find the last word in a sentence.
>>> last_word('abc')
diff --git a/mycli/packages/special/iocommands.py b/mycli/packages/special/iocommands.py
index 11dca8d..58066b8 100644
--- a/mycli/packages/special/iocommands.py
+++ b/mycli/packages/special/iocommands.py
@@ -8,6 +8,7 @@ from io import open
from time import sleep
import click
+import pyperclip
import sqlparse
from . import export
@@ -23,6 +24,8 @@ PAGER_ENABLED = True
tee_file = None
once_file = None
written_to_once_file = False
+pipe_once_process = None
+written_to_pipe_once_process = False
delimiter_command = DelimiterCommand()
@@ -115,7 +118,7 @@ def get_editor_query(sql):
# The reason we can't simply do .strip('\e') is that it strips characters,
# not a substring. So it'll strip "e" in the end of the sql also!
# Ex: "select * from style\e" -> "select * from styl".
- pattern = re.compile('(^\\\e|\\\e$)')
+ pattern = re.compile(r'(^\\e|\\e$)')
while pattern.search(sql):
sql = pattern.sub('', sql)
@@ -159,6 +162,47 @@ def open_external_editor(filename=None, sql=None):
return (query, message)
+@export
+def clip_command(command):
+ """Is this a clip command?
+
+ :param command: string
+
+ """
+ # It is possible to have `\clip` or `SELECT * FROM \clip`. So we check
+ # for both conditions.
+ return command.strip().endswith('\\clip') or command.strip().startswith('\\clip')
+
+
+@export
+def get_clip_query(sql):
+ """Get the query part of a clip command."""
+ sql = sql.strip()
+
+ # The reason we can't simply do .strip('\clip') is that it strips characters,
+ # not a substring. So it'll strip "c" in the end of the sql also!
+ pattern = re.compile(r'(^\\clip|\\clip$)')
+ while pattern.search(sql):
+ sql = pattern.sub('', sql)
+
+ return sql
+
+
+@export
+def copy_query_to_clipboard(sql=None):
+ """Send query to the clipboard."""
+
+ sql = sql or ''
+ message = None
+
+ try:
+ pyperclip.copy(u'{sql}'.format(sql=sql))
+ except RuntimeError as e:
+ message = 'Error clipping query: %s.' % e.strerror
+
+ return message
+
+
@special_command('\\f', '\\f [name [args..]]', 'List or execute favorite queries.', arg_type=PARSED_QUERY, case_sensitive=True)
def execute_favorite_query(cur, arg, **_):
"""Returns (title, rows, headers, status)"""
@@ -213,7 +257,7 @@ def subst_favorite_query_args(query, args):
query = query.replace(subst_var, val)
- match = re.search('\\$\d+', query)
+ match = re.search(r'\$\d+', query)
if match:
return[None, 'missing substitution for ' + match.group(0) + ' in query:\n ' + query]
@@ -337,7 +381,11 @@ def write_tee(output):
def set_once(arg, **_):
global once_file, written_to_once_file
- once_file = parseargfile(arg)
+ try:
+ once_file = open(**parseargfile(arg))
+ except (IOError, OSError) as e:
+ raise OSError("Cannot write to file '{}': {}".format(
+ e.filename, e.strerror))
written_to_once_file = False
return [(None, None, None, "")]
@@ -347,26 +395,68 @@ def set_once(arg, **_):
def write_once(output):
global once_file, written_to_once_file
if output and once_file:
- try:
- f = open(**once_file)
- except (IOError, OSError) as e:
- once_file = None
- raise OSError("Cannot write to file '{}': {}".format(
- e.filename, e.strerror))
- with f:
- click.echo(output, file=f, nl=False)
- click.echo(u"\n", file=f, nl=False)
+ click.echo(output, file=once_file, nl=False)
+ click.echo(u"\n", file=once_file, nl=False)
+ once_file.flush()
written_to_once_file = True
@export
def unset_once_if_written():
"""Unset the once file, if it has been written to."""
- global once_file
- if written_to_once_file:
+ global once_file, written_to_once_file
+ if written_to_once_file and once_file:
+ once_file.close()
once_file = None
+@special_command('\\pipe_once', '\\| command',
+ 'Send next result to a subprocess.',
+ aliases=('\\|', ))
+def set_pipe_once(arg, **_):
+ global pipe_once_process, written_to_pipe_once_process
+ pipe_once_cmd = shlex.split(arg)
+ if len(pipe_once_cmd) == 0:
+ raise OSError("pipe_once requires a command")
+ written_to_pipe_once_process = False
+ pipe_once_process = subprocess.Popen(pipe_once_cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=1,
+ encoding='UTF-8',
+ universal_newlines=True)
+ return [(None, None, None, "")]
+
+
+@export
+def write_pipe_once(output):
+ global pipe_once_process, written_to_pipe_once_process
+ if output and pipe_once_process:
+ try:
+ click.echo(output, file=pipe_once_process.stdin, nl=False)
+ click.echo(u"\n", file=pipe_once_process.stdin, nl=False)
+ except (IOError, OSError) as e:
+ pipe_once_process.terminate()
+ raise OSError(
+ "Failed writing to pipe_once subprocess: {}".format(e.strerror))
+ written_to_pipe_once_process = True
+
+
+@export
+def unset_pipe_once_if_written():
+ """Unset the pipe_once cmd, if it has been written to."""
+ global pipe_once_process, written_to_pipe_once_process
+ if written_to_pipe_once_process:
+ (stdout_data, stderr_data) = pipe_once_process.communicate()
+ if len(stdout_data) > 0:
+ print(stdout_data.rstrip(u"\n"))
+ if len(stderr_data) > 0:
+ print(stderr_data.rstrip(u"\n"))
+ pipe_once_process = None
+ written_to_pipe_once_process = False
+
+
@special_command(
'watch',
'watch [seconds] [-c] query',
diff --git a/mycli/packages/special/main.py b/mycli/packages/special/main.py
index dddba66..ab04f30 100644
--- a/mycli/packages/special/main.py
+++ b/mycli/packages/special/main.py
@@ -112,6 +112,8 @@ def quit(*_args):
@special_command('\\e', '\\e', 'Edit command with editor (uses $EDITOR).',
arg_type=NO_QUERY, case_sensitive=True)
+@special_command('\\clip', '\\clip', 'Copy query to the system clipboard.',
+ arg_type=NO_QUERY, case_sensitive=True)
@special_command('\\G', '\\G', 'Display current query results vertically.',
arg_type=NO_QUERY, case_sensitive=True)
def stub():
diff --git a/mycli/sqlcompleter.py b/mycli/sqlcompleter.py
index 20611be..73b9b44 100644
--- a/mycli/sqlcompleter.py
+++ b/mycli/sqlcompleter.py
@@ -59,7 +59,7 @@ class SQLCompleter(Completer):
self.reserved_words = set()
for x in self.keywords:
self.reserved_words.update(x.split())
- self.name_pattern = compile("^[_a-z][_a-z0-9\$]*$")
+ self.name_pattern = compile(r"^[_a-z][_a-z0-9\$]*$")
self.special_commands = []
self.table_formats = supported_formats
diff --git a/mycli/sqlexecute.py b/mycli/sqlexecute.py
index c68af0f..7534982 100644
--- a/mycli/sqlexecute.py
+++ b/mycli/sqlexecute.py
@@ -42,7 +42,7 @@ class SQLExecute(object):
def __init__(self, database, user, password, host, port, socket, charset,
local_infile, ssl, ssh_user, ssh_host, ssh_port, ssh_password,
- ssh_key_filename):
+ ssh_key_filename, init_command=None):
self.dbname = database
self.user = user
self.password = password
@@ -59,12 +59,13 @@ class SQLExecute(object):
self.ssh_port = ssh_port
self.ssh_password = ssh_password
self.ssh_key_filename = ssh_key_filename
+ self.init_command = init_command
self.connect()
def connect(self, database=None, user=None, password=None, host=None,
port=None, socket=None, charset=None, local_infile=None,
ssl=None, ssh_host=None, ssh_port=None, ssh_user=None,
- ssh_password=None, ssh_key_filename=None):
+ ssh_password=None, ssh_key_filename=None, init_command=None):
db = (database or self.dbname)
user = (user or self.user)
password = (password or self.password)
@@ -79,6 +80,7 @@ class SQLExecute(object):
ssh_port = (ssh_port or self.ssh_port)
ssh_password = (ssh_password or self.ssh_password)
ssh_key_filename = (ssh_key_filename or self.ssh_key_filename)
+ init_command = (init_command or self.init_command)
_logger.debug(
'Connection DB Params: \n'
'\tdatabase: %r'
@@ -93,9 +95,11 @@ class SQLExecute(object):
'\tssh_host: %r'
'\tssh_port: %r'
'\tssh_password: %r'
- '\tssh_key_filename: %r',
+ '\tssh_key_filename: %r'
+ '\tinit_command: %r',
db, user, host, port, socket, charset, local_infile, ssl,
- ssh_user, ssh_host, ssh_port, ssh_password, ssh_key_filename
+ ssh_user, ssh_host, ssh_port, ssh_password, ssh_key_filename,
+ init_command
)
conv = conversions.copy()
conv.update({
@@ -110,12 +114,16 @@ class SQLExecute(object):
if ssh_host:
defer_connect = True
+ client_flag = pymysql.constants.CLIENT.INTERACTIVE
+ if init_command and len(list(special.split_queries(init_command))) > 1:
+ client_flag |= pymysql.constants.CLIENT.MULTI_STATEMENTS
+
conn = pymysql.connect(
database=db, user=user, password=password, host=host, port=port,
unix_socket=socket, use_unicode=True, charset=charset,
- autocommit=True, client_flag=pymysql.constants.CLIENT.INTERACTIVE,
+ autocommit=True, client_flag=client_flag,
local_infile=local_infile, conv=conv, ssl=ssl, program_name="mycli",
- defer_connect=defer_connect
+ defer_connect=defer_connect, init_command=init_command
)
if ssh_host:
@@ -146,6 +154,7 @@ class SQLExecute(object):
self.socket = socket
self.charset = charset
self.ssl = ssl
+ self.init_command = init_command
# retrieve connection id
self.reset_connection_id()