You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
17 KiB
471 lines
17 KiB
diff --git a/.travis.yml b/.travis.yml
|
|
index 0afb5cc..182dea7 100644
|
|
--- a/.travis.yml
|
|
+++ b/.travis.yml
|
|
@@ -12,7 +12,7 @@ matrix:
|
|
|
|
install:
|
|
- pip install -r requirements-dev.txt
|
|
- - pip install -e .
|
|
+ - pip install --no-cache-dir -e .
|
|
- sudo rm -f /etc/mysql/conf.d/performance-schema.cnf
|
|
- sudo service mysql restart
|
|
|
|
diff --git a/README.md b/README.md
|
|
index efe804d..d5a0687 100644
|
|
--- a/README.md
|
|
+++ b/README.md
|
|
@@ -96,6 +96,7 @@ $ sudo apt-get install mycli # Only on debian or ubuntu
|
|
--local-infile BOOLEAN Enable/disable LOAD DATA LOCAL INFILE.
|
|
--login-path TEXT Read this path from the login file.
|
|
-e, --execute TEXT Execute command and quit.
|
|
+ --init-command TEXT SQL statement to execute after connecting.
|
|
--help Show this message and exit.
|
|
|
|
Features
|
|
diff --git a/changelog.md b/changelog.md
|
|
index a4fea35..508c801 100644
|
|
--- a/changelog.md
|
|
+++ b/changelog.md
|
|
@@ -1,9 +1,22 @@
|
|
+TBD
|
|
+===
|
|
+
|
|
+Features:
|
|
+---------
|
|
+
|
|
+* Add an option `--init-command` to execute SQL after connecting (Thanks: [KITAGAWA Yasutaka]).
|
|
+
|
|
+Bug Fixes:
|
|
+----------
|
|
+* Fixed compatibility with sqlparse 0.4 (Thanks: [mtorromeo]).
|
|
+
|
|
1.22.2
|
|
======
|
|
|
|
Bug Fixes:
|
|
----------
|
|
-* Make the `pwd` module optional.
|
|
+
|
|
+* Make the `pwd` module optional.
|
|
|
|
1.22.1
|
|
======
|
|
@@ -18,6 +31,11 @@ Features:
|
|
* Add an option `--list-ssh-config` to list ssh configurations.
|
|
* Add an option `--ssh-config-path` to choose ssh configuration path.
|
|
|
|
+Bug Fixes:
|
|
+----------
|
|
+
|
|
+* Fix specifying empty password with `--password=''` when config file has a password set (Thanks: [Zach DeCook]).
|
|
+
|
|
|
|
1.21.1
|
|
======
|
|
@@ -28,6 +46,7 @@ Bug Fixes:
|
|
|
|
* Fix broken auto-completion for favorite queries (Thanks: [Amjith]).
|
|
* Fix undefined variable exception when running with --no-warn (Thanks: [Georgy Frolov])
|
|
+* Support setting color for null value (Thanks: [laixintao])
|
|
|
|
1.21.0
|
|
======
|
|
@@ -768,3 +787,6 @@ Bug Fixes:
|
|
[François Pietka]: https://github.com/fpietka
|
|
[Frederic Aoustin]: https://github.com/fraoustin
|
|
[Georgy Frolov]: https://github.com/pasenor
|
|
+[Zach DeCook]: https://zachdecook.com
|
|
+[laixintao]: https://github.com/laixintao
|
|
+[mtorromeo]: https://github.com/mtorromeo
|
|
diff --git a/mycli/AUTHORS b/mycli/AUTHORS
|
|
index b3636d9..a1204b0 100644
|
|
--- a/mycli/AUTHORS
|
|
+++ b/mycli/AUTHORS
|
|
@@ -72,6 +72,10 @@ Contributors:
|
|
* Jakub Boukal
|
|
* Takeshi D. Itoh
|
|
* laixintao
|
|
+ * Zach DeCook
|
|
+ * kevinhwang91
|
|
+ * KITAGAWA Yasutaka
|
|
+ * Massimiliano Torromeo
|
|
|
|
Creator:
|
|
--------
|
|
diff --git a/mycli/clistyle.py b/mycli/clistyle.py
|
|
index c94f793..293f0f4 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',
|
|
}
|
|
diff --git a/mycli/main.py b/mycli/main.py
|
|
index 03797a0..dffd724 100755
|
|
--- a/mycli/main.py
|
|
+++ b/mycli/main.py
|
|
@@ -98,7 +98,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 +152,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
|
|
@@ -363,7 +363,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,
|
|
@@ -396,7 +396,7 @@ class MyCli(object):
|
|
port = port or cnf['port']
|
|
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 +420,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 +429,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
|
|
@@ -1051,6 +1051,8 @@ 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.argument('database', default='', nargs=1)
|
|
def cli(database, user, host, port, socket, password, dbname,
|
|
version, verbose, prompt, logfile, defaults_group_suffix,
|
|
@@ -1058,7 +1060,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):
|
|
"""A MySQL terminal client with auto-completion and syntax highlighting.
|
|
|
|
\b
|
|
@@ -1182,7 +1185,8 @@ 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
|
|
)
|
|
|
|
mycli.logger.debug('Launch Params: \n'
|
|
diff --git a/mycli/myclirc b/mycli/myclirc
|
|
index 534b201..ba3ea1e 100644
|
|
--- a/mycli/myclirc
|
|
+++ b/mycli/myclirc
|
|
@@ -111,6 +111,7 @@ bottom-toolbar.transaction.failed = 'bg:#222222 #ff005f bold'
|
|
output.header = "#00ff5f bold"
|
|
output.odd-row = ""
|
|
output.even-row = ""
|
|
+output.null = "#808080"
|
|
|
|
# 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/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()
|
|
|
|
diff --git a/requirements-dev.txt b/requirements-dev.txt
|
|
index 8e206a5..7a38ed5 100644
|
|
--- a/requirements-dev.txt
|
|
+++ b/requirements-dev.txt
|
|
@@ -3,8 +3,8 @@ pytest!=3.3.0
|
|
pytest-cov==2.4.0
|
|
tox
|
|
twine==1.12.1
|
|
-behave
|
|
-pexpect
|
|
+behave>=1.2.4
|
|
+pexpect==3.3
|
|
coverage==5.0.4
|
|
codecov==2.0.9
|
|
autopep8==1.3.3
|
|
diff --git a/setup.py b/setup.py
|
|
index 156cd1a..fbab22e 100755
|
|
--- a/setup.py
|
|
+++ b/setup.py
|
|
@@ -24,7 +24,7 @@ install_requirements = [
|
|
'sqlparse>=0.3.0,<0.4.0',
|
|
'configobj >= 5.0.5',
|
|
'cryptography >= 1.0.0',
|
|
- 'cli_helpers[styles] > 1.1.0',
|
|
+ 'cli_helpers[styles] >= 2.0.1',
|
|
]
|
|
|
|
|
|
@@ -65,7 +65,7 @@ class test(TestCommand):
|
|
def initialize_options(self):
|
|
TestCommand.initialize_options(self)
|
|
self.pytest_args = ''
|
|
- self.behave_args = ''
|
|
+ self.behave_args = '--no-capture'
|
|
|
|
def run_tests(self):
|
|
unit_test_errno = subprocess.call(
|
|
diff --git a/test/features/environment.py b/test/features/environment.py
|
|
index 1a49dbe..cb35140 100644
|
|
--- a/test/features/environment.py
|
|
+++ b/test/features/environment.py
|
|
@@ -16,7 +16,7 @@ def before_all(context):
|
|
os.environ['LINES'] = "100"
|
|
os.environ['COLUMNS'] = "100"
|
|
os.environ['EDITOR'] = 'ex'
|
|
- os.environ['LC_ALL'] = 'en_US.utf8'
|
|
+ os.environ['LC_ALL'] = 'en_US.UTF-8'
|
|
os.environ['PROMPT_TOOLKIT_NO_CPR'] = '1'
|
|
|
|
test_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
|
@@ -118,11 +118,12 @@ def after_scenario(context, _):
|
|
host = context.conf['host']
|
|
dbname = context.currentdb
|
|
context.cli.expect_exact(
|
|
- '{0}@{1}:{2}> '.format(
|
|
+ '{0}@{1}:{2}>'.format(
|
|
user, host, dbname
|
|
),
|
|
timeout=5
|
|
)
|
|
+ context.cli.sendcontrol('c')
|
|
context.cli.sendcontrol('d')
|
|
context.cli.expect_exact(pexpect.EOF, timeout=5)
|
|
|
|
diff --git a/test/features/steps/crud_database.py b/test/features/steps/crud_database.py
|
|
index a0bfa53..be6dec0 100644
|
|
--- a/test/features/steps/crud_database.py
|
|
+++ b/test/features/steps/crud_database.py
|
|
@@ -64,15 +64,13 @@ def step_see_prompt(context):
|
|
user = context.conf['user']
|
|
host = context.conf['host']
|
|
dbname = context.currentdb
|
|
- wrappers.expect_exact(context, '{0}@{1}:{2}> '.format(
|
|
- user, host, dbname), timeout=5)
|
|
- context.atprompt = True
|
|
+ wrappers.wait_prompt(context, '{0}@{1}:{2}> '.format(user, host, dbname))
|
|
|
|
|
|
@then('we see help output')
|
|
def step_see_help(context):
|
|
for expected_line in context.fixture_data['help_commands.txt']:
|
|
- wrappers.expect_exact(context, expected_line + '\r\n', timeout=1)
|
|
+ wrappers.expect_exact(context, expected_line, timeout=1)
|
|
|
|
|
|
@then('we see database created')
|
|
@@ -96,10 +94,7 @@ def step_see_db_dropped_no_default(context):
|
|
context.currentdb = None
|
|
|
|
wrappers.expect_exact(context, 'Query OK, 0 rows affected', timeout=2)
|
|
- wrappers.expect_exact(context, '{0}@{1}:{2}> '.format(
|
|
- user, host, database), timeout=5)
|
|
-
|
|
- context.atprompt = True
|
|
+ wrappers.wait_prompt(context, '{0}@{1}:{2}>'.format(user, host, database))
|
|
|
|
|
|
@then('we see database connected')
|
|
diff --git a/test/features/steps/wrappers.py b/test/features/steps/wrappers.py
|
|
index 565ca59..de833dd 100644
|
|
--- a/test/features/steps/wrappers.py
|
|
+++ b/test/features/steps/wrappers.py
|
|
@@ -88,7 +88,7 @@ def wait_prompt(context, prompt=None):
|
|
user = context.conf['user']
|
|
host = context.conf['host']
|
|
dbname = context.currentdb
|
|
- prompt = '{0}@{1}:{2}> '.format(
|
|
+ prompt = '{0}@{1}:{2}>'.format(
|
|
user, host, dbname),
|
|
expect_exact(context, prompt, timeout=5)
|
|
context.atprompt = True
|
|
diff --git a/test/test_main.py b/test/test_main.py
|
|
index 3f92bd1..707c359 100644
|
|
--- a/test/test_main.py
|
|
+++ b/test/test_main.py
|
|
@@ -492,3 +492,37 @@ def test_ssh_config(monkeypatch):
|
|
MockMyCli.connect_args["ssh_host"] == "arg_host" and \
|
|
MockMyCli.connect_args["ssh_port"] == 3 and \
|
|
MockMyCli.connect_args["ssh_key_filename"] == "/path/to/key"
|
|
+
|
|
+
|
|
+@dbtest
|
|
+def test_init_command_arg(executor):
|
|
+ init_command = "set sql_select_limit=1000"
|
|
+ sql = 'show variables like "sql_select_limit";'
|
|
+ runner = CliRunner()
|
|
+ result = runner.invoke(
|
|
+ cli, args=CLI_ARGS + ["--init-command", init_command], input=sql
|
|
+ )
|
|
+
|
|
+ expected = "sql_select_limit\t1000\n"
|
|
+ assert result.exit_code == 0
|
|
+ assert expected in result.output
|
|
+
|
|
+
|
|
+@dbtest
|
|
+def test_init_command_multiple_arg(executor):
|
|
+ init_command = 'set sql_select_limit=2000; set max_join_size=20000'
|
|
+ sql = (
|
|
+ 'show variables like "sql_select_limit";\n'
|
|
+ 'show variables like "max_join_size"'
|
|
+ )
|
|
+ runner = CliRunner()
|
|
+ result = runner.invoke(
|
|
+ cli, args=CLI_ARGS + ['--init-command', init_command], input=sql
|
|
+ )
|
|
+
|
|
+ expected_sql_select_limit = 'sql_select_limit\t2000\n'
|
|
+ expected_max_join_size = 'max_join_size\t20000\n'
|
|
+
|
|
+ assert result.exit_code == 0
|
|
+ assert expected_sql_select_limit in result.output
|
|
+ assert expected_max_join_size in result.output
|
|
diff --git a/test/test_tabular_output.py b/test/test_tabular_output.py
|
|
index 7d7d000..c20c7de 100644
|
|
--- a/test/test_tabular_output.py
|
|
+++ b/test/test_tabular_output.py
|
|
@@ -16,7 +16,7 @@ from pymysql.constants import FIELD_TYPE
|
|
@pytest.fixture
|
|
def mycli():
|
|
cli = MyCli()
|
|
- cli.connect(None, USER, PASSWORD, HOST, PORT, None)
|
|
+ cli.connect(None, USER, PASSWORD, HOST, PORT, None, init_command=None)
|
|
return cli
|
|
|
|
|