Merging upstream version 1.30.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
1b2f180fc6
commit
1fa9e0bfa5
11 changed files with 125 additions and 17 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -42,6 +42,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PYTEST_PASSWORD: root
|
PYTEST_PASSWORD: root
|
||||||
PYTEST_HOST: 127.0.0.1
|
PYTEST_HOST: 127.0.0.1
|
||||||
|
TERM: xterm
|
||||||
run: |
|
run: |
|
||||||
uv run tox -e py${{ matrix.python-version }}
|
uv run tox -e py${{ matrix.python-version }}
|
||||||
|
|
||||||
|
|
10
changelog.md
10
changelog.md
|
@ -1,3 +1,13 @@
|
||||||
|
1.30.0 (2025/04/19)
|
||||||
|
===================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* DSN specific init-command in myclirc. Fixes (#1195)
|
||||||
|
* Add `\\g` to force the horizontal output.
|
||||||
|
|
||||||
|
|
||||||
1.29.2 (2024/12/11)
|
1.29.2 (2024/12/11)
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ Contributors:
|
||||||
* Daniel West
|
* Daniel West
|
||||||
* Daniël van Eeden
|
* Daniël van Eeden
|
||||||
* Fabrizio Gennari
|
* Fabrizio Gennari
|
||||||
|
* FatBoyXPC
|
||||||
* François Pietka
|
* François Pietka
|
||||||
* Frederic Aoustin
|
* Frederic Aoustin
|
||||||
* Georgy Frolov
|
* Georgy Frolov
|
||||||
|
|
|
@ -10,6 +10,8 @@ import re
|
||||||
import stat
|
import stat
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from pygments.lexer import combined
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pwd import getpwuid
|
from pwd import getpwuid
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -524,14 +526,14 @@ class MyCli(object):
|
||||||
port = int(port)
|
port = int(port)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.echo("Error: Invalid port number: '{0}'.".format(port), err=True, fg="red")
|
self.echo("Error: Invalid port number: '{0}'.".format(port), err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
_connect()
|
_connect()
|
||||||
except Exception as e: # Connecting to a database could fail.
|
except Exception as e: # Connecting to a database could fail.
|
||||||
self.logger.debug("Database connection failed: %r.", e)
|
self.logger.debug("Database connection failed: %r.", e)
|
||||||
self.logger.error("traceback: %r", traceback.format_exc())
|
self.logger.error("traceback: %r", traceback.format_exc())
|
||||||
self.echo(str(e), err=True, fg="red")
|
self.echo(str(e), err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def get_password_from_file(self, password_file):
|
def get_password_from_file(self, password_file):
|
||||||
password_from_file = None
|
password_from_file = None
|
||||||
|
@ -674,6 +676,7 @@ class MyCli(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
special.set_expanded_output(False)
|
special.set_expanded_output(False)
|
||||||
|
special.set_forced_horizontal_output(False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
text = self.handle_editor_command(text)
|
text = self.handle_editor_command(text)
|
||||||
|
@ -743,6 +746,9 @@ class MyCli(object):
|
||||||
else:
|
else:
|
||||||
max_width = None
|
max_width = None
|
||||||
|
|
||||||
|
if special.forced_horizontal():
|
||||||
|
max_width = None
|
||||||
|
|
||||||
formatted = self.format_output(title, cur, headers, special.is_expanded_output(), max_width)
|
formatted = self.format_output(title, cur, headers, special.is_expanded_output(), max_width)
|
||||||
|
|
||||||
t = time() - start
|
t = time() - start
|
||||||
|
@ -1224,10 +1230,10 @@ def cli(
|
||||||
alias_dsn = mycli.config["alias_dsn"]
|
alias_dsn = mycli.config["alias_dsn"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
click.secho("Invalid DSNs found in the config file. " 'Please check the "[alias_dsn]" section in myclirc.', err=True, fg="red")
|
click.secho("Invalid DSNs found in the config file. " 'Please check the "[alias_dsn]" section in myclirc.', err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
click.secho(str(e), err=True, fg="red")
|
click.secho(str(e), err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
for alias, value in alias_dsn.items():
|
for alias, value in alias_dsn.items():
|
||||||
if verbose:
|
if verbose:
|
||||||
click.secho("{} : {}".format(alias, value))
|
click.secho("{} : {}".format(alias, value))
|
||||||
|
@ -1262,9 +1268,13 @@ def cli(
|
||||||
|
|
||||||
dsn_uri = None
|
dsn_uri = None
|
||||||
|
|
||||||
# Treat the database argument as a DSN alias if we're missing
|
# Treat the database argument as a DSN alias only if it matches a configured alias
|
||||||
# other connection information.
|
if (
|
||||||
if mycli.config["alias_dsn"] and database and "://" not in database and not any([user, password, host, port, login_path]):
|
database
|
||||||
|
and "://" not in database
|
||||||
|
and not any([user, password, host, port, login_path])
|
||||||
|
and database in mycli.config.get("alias_dsn", {})
|
||||||
|
):
|
||||||
dsn, database = database, ""
|
dsn, database = database, ""
|
||||||
|
|
||||||
if database and "://" in database:
|
if database and "://" in database:
|
||||||
|
@ -1279,7 +1289,7 @@ def cli(
|
||||||
err=True,
|
err=True,
|
||||||
fg="red",
|
fg="red",
|
||||||
)
|
)
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
mycli.dsn_alias = dsn
|
mycli.dsn_alias = dsn
|
||||||
|
|
||||||
|
@ -1306,6 +1316,29 @@ def cli(
|
||||||
ssh_key_filename = ssh_key_filename if ssh_key_filename else ssh_config.get("identityfile", [None])[0]
|
ssh_key_filename = ssh_key_filename if ssh_key_filename else ssh_config.get("identityfile", [None])[0]
|
||||||
|
|
||||||
ssh_key_filename = ssh_key_filename and os.path.expanduser(ssh_key_filename)
|
ssh_key_filename = ssh_key_filename and os.path.expanduser(ssh_key_filename)
|
||||||
|
# Merge init-commands: global, DSN-specific, then CLI
|
||||||
|
init_cmds = []
|
||||||
|
# 1) Global init-commands
|
||||||
|
global_section = mycli.config.get("init-commands", {})
|
||||||
|
for _, val in global_section.items():
|
||||||
|
if isinstance(val, (list, tuple)):
|
||||||
|
init_cmds.extend(val)
|
||||||
|
elif val:
|
||||||
|
init_cmds.append(val)
|
||||||
|
# 2) DSN-specific init-commands
|
||||||
|
if dsn:
|
||||||
|
alias_section = mycli.config.get("alias_dsn.init-commands", {})
|
||||||
|
if dsn in alias_section:
|
||||||
|
val = alias_section.get(dsn)
|
||||||
|
if isinstance(val, (list, tuple)):
|
||||||
|
init_cmds.extend(val)
|
||||||
|
elif val:
|
||||||
|
init_cmds.append(val)
|
||||||
|
# 3) CLI-provided init_command
|
||||||
|
if init_command:
|
||||||
|
init_cmds.append(init_command)
|
||||||
|
|
||||||
|
combined_init_cmd = "; ".join(cmd.strip() for cmd in init_cmds if cmd)
|
||||||
|
|
||||||
mycli.connect(
|
mycli.connect(
|
||||||
database=database,
|
database=database,
|
||||||
|
@ -1321,11 +1354,14 @@ def cli(
|
||||||
ssh_port=ssh_port,
|
ssh_port=ssh_port,
|
||||||
ssh_password=ssh_password,
|
ssh_password=ssh_password,
|
||||||
ssh_key_filename=ssh_key_filename,
|
ssh_key_filename=ssh_key_filename,
|
||||||
init_command=init_command,
|
init_command=combined_init_cmd,
|
||||||
charset=charset,
|
charset=charset,
|
||||||
password_file=password_file,
|
password_file=password_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if combined_init_cmd:
|
||||||
|
click.echo("Executing init-command: %s" % combined_init_cmd, err=True)
|
||||||
|
|
||||||
mycli.logger.debug("Launch Params: \n" "\tdatabase: %r" "\tuser: %r" "\thost: %r" "\tport: %r", database, user, host, port)
|
mycli.logger.debug("Launch Params: \n" "\tdatabase: %r" "\tuser: %r" "\thost: %r" "\tport: %r", database, user, host, port)
|
||||||
|
|
||||||
# --execute argument
|
# --execute argument
|
||||||
|
@ -1342,10 +1378,10 @@ def cli(
|
||||||
mycli.formatter.format_name = "tsv"
|
mycli.formatter.format_name = "tsv"
|
||||||
|
|
||||||
mycli.run_query(execute)
|
mycli.run_query(execute)
|
||||||
exit(0)
|
sys.exit(0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
click.secho(str(e), err=True, fg="red")
|
click.secho(str(e), err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if sys.stdin.isatty():
|
if sys.stdin.isatty():
|
||||||
mycli.run_cli()
|
mycli.run_cli()
|
||||||
|
@ -1357,7 +1393,7 @@ def cli(
|
||||||
click.secho("Failed! Ran out of memory.", err=True, fg="red")
|
click.secho("Failed! Ran out of memory.", err=True, fg="red")
|
||||||
click.secho("You might want to try the official mysql client.", err=True, fg="red")
|
click.secho("You might want to try the official mysql client.", err=True, fg="red")
|
||||||
click.secho("Sorry... :(", err=True, fg="red")
|
click.secho("Sorry... :(", err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if mycli.destructive_warning and is_destructive(stdin_text):
|
if mycli.destructive_warning and is_destructive(stdin_text):
|
||||||
try:
|
try:
|
||||||
|
@ -1366,7 +1402,7 @@ def cli(
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
mycli.logger.warning("Unable to open TTY as stdin.")
|
mycli.logger.warning("Unable to open TTY as stdin.")
|
||||||
if not warn_confirmed:
|
if not warn_confirmed:
|
||||||
exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_line = True
|
new_line = True
|
||||||
|
@ -1377,10 +1413,10 @@ def cli(
|
||||||
mycli.formatter.format_name = "tsv"
|
mycli.formatter.format_name = "tsv"
|
||||||
|
|
||||||
mycli.run_query(stdin_text, new_line=new_line)
|
mycli.run_query(stdin_text, new_line=new_line)
|
||||||
exit(0)
|
sys.exit(0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
click.secho(str(e), err=True, fg="red")
|
click.secho(str(e), err=True, fg="red")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def need_completion_refresh(queries):
|
def need_completion_refresh(queries):
|
||||||
|
|
|
@ -151,9 +151,22 @@ output.null = "#808080"
|
||||||
# sql.whitespace = ''
|
# sql.whitespace = ''
|
||||||
|
|
||||||
# Favorite queries.
|
# Favorite queries.
|
||||||
|
# You can add your favorite queries here. They will be available in the
|
||||||
|
# REPL when you type `\f` or `\f <query_name>`.
|
||||||
[favorite_queries]
|
[favorite_queries]
|
||||||
|
# example = "SELECT * FROM example_table WHERE id = 1"
|
||||||
|
|
||||||
|
# Initial commands to execute when connecting to any database.
|
||||||
|
[init-commands]
|
||||||
|
# read_only = "SET SESSION TRANSACTION READ ONLY"
|
||||||
|
|
||||||
|
|
||||||
# Use the -d option to reference a DSN.
|
# Use the -d option to reference a DSN.
|
||||||
# Special characters in passwords and other strings can be escaped with URL encoding.
|
# Special characters in passwords and other strings can be escaped with URL encoding.
|
||||||
[alias_dsn]
|
[alias_dsn]
|
||||||
# example_dsn = mysql://[user[:password]@][host][:port][/dbname]
|
# example_dsn = mysql://[user[:password]@][host][:port][/dbname]
|
||||||
|
|
||||||
|
# Initial commands to execute when connecting to a DSN alias.
|
||||||
|
[alias_dsn.init-commands]
|
||||||
|
# Define one or more SQL statements per alias (semicolon-separated).
|
||||||
|
# example_dsn = "SET sql_select_limit=1000; SET time_zone='+00:00'"
|
||||||
|
|
|
@ -20,6 +20,7 @@ from mycli.packages.prompt_utils import confirm_destructive_query
|
||||||
|
|
||||||
TIMING_ENABLED = False
|
TIMING_ENABLED = False
|
||||||
use_expanded_output = False
|
use_expanded_output = False
|
||||||
|
force_horizontal_output = False
|
||||||
PAGER_ENABLED = True
|
PAGER_ENABLED = True
|
||||||
tee_file = None
|
tee_file = None
|
||||||
once_file = None
|
once_file = None
|
||||||
|
@ -97,6 +98,14 @@ def set_expanded_output(val):
|
||||||
def is_expanded_output():
|
def is_expanded_output():
|
||||||
return use_expanded_output
|
return use_expanded_output
|
||||||
|
|
||||||
|
@export
|
||||||
|
def set_forced_horizontal_output(val):
|
||||||
|
global force_horizontal_output
|
||||||
|
force_horizontal_output = val
|
||||||
|
|
||||||
|
@export
|
||||||
|
def forced_horizontal():
|
||||||
|
return force_horizontal_output
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ class SQLExecute(object):
|
||||||
ssl=ssl_context,
|
ssl=ssl_context,
|
||||||
program_name="mycli",
|
program_name="mycli",
|
||||||
defer_connect=defer_connect,
|
defer_connect=defer_connect,
|
||||||
init_command=init_command,
|
init_command=init_command or None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ssh_host:
|
if ssh_host:
|
||||||
|
@ -298,6 +298,12 @@ class SQLExecute(object):
|
||||||
if sql.endswith("\\G"):
|
if sql.endswith("\\G"):
|
||||||
special.set_expanded_output(True)
|
special.set_expanded_output(True)
|
||||||
sql = sql[:-2].strip()
|
sql = sql[:-2].strip()
|
||||||
|
# \g is treated specially since we might want collapsed output when
|
||||||
|
# auto vertical output is enabled
|
||||||
|
elif sql.endswith('\\g'):
|
||||||
|
special.set_expanded_output(False)
|
||||||
|
special.set_forced_horizontal_output(True)
|
||||||
|
sql = sql[:-2].strip()
|
||||||
|
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
try: # Special command
|
try: # Special command
|
||||||
|
|
|
@ -9,7 +9,7 @@ authors = [{ name = "Mycli Core Team", email = "mycli-dev@googlegroups.com" }]
|
||||||
urls = { homepage = "http://mycli.net" }
|
urls = { homepage = "http://mycli.net" }
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click >= 7.0",
|
"click >= 7.0,<8.1.8",
|
||||||
"cryptography >= 1.0.0",
|
"cryptography >= 1.0.0",
|
||||||
"Pygments>=1.6",
|
"Pygments>=1.6",
|
||||||
"prompt_toolkit>=3.0.6,<4.0.0",
|
"prompt_toolkit>=3.0.6,<4.0.0",
|
||||||
|
|
14
test/myclirc
14
test/myclirc
|
@ -151,11 +151,25 @@ output.null = "#808080"
|
||||||
# sql.whitespace = ''
|
# sql.whitespace = ''
|
||||||
|
|
||||||
# Favorite queries.
|
# Favorite queries.
|
||||||
|
# You can add your favorite queries here. They will be available in the
|
||||||
|
# REPL when you type `\f` or `\f <query_name>`.
|
||||||
[favorite_queries]
|
[favorite_queries]
|
||||||
check = 'select "✔"'
|
check = 'select "✔"'
|
||||||
foo_args = 'SELECT $1, "$2", "$3"'
|
foo_args = 'SELECT $1, "$2", "$3"'
|
||||||
|
# example = "SELECT * FROM example_table WHERE id = 1"
|
||||||
|
|
||||||
|
# Initial commands to execute when connecting to any database.
|
||||||
|
[init-commands]
|
||||||
|
# read_only = "SET SESSION TRANSACTION READ ONLY"
|
||||||
|
global_limit = "set sql_select_limit=9999"
|
||||||
|
|
||||||
|
|
||||||
# Use the -d option to reference a DSN.
|
# Use the -d option to reference a DSN.
|
||||||
# Special characters in passwords and other strings can be escaped with URL encoding.
|
# Special characters in passwords and other strings can be escaped with URL encoding.
|
||||||
[alias_dsn]
|
[alias_dsn]
|
||||||
# example_dsn = mysql://[user[:password]@][host][:port][/dbname]
|
# example_dsn = mysql://[user[:password]@][host][:port][/dbname]
|
||||||
|
|
||||||
|
# Initial commands to execute when connecting to a DSN alias.
|
||||||
|
[alias_dsn.init-commands]
|
||||||
|
# Define one or more SQL statements per alias (semicolon-separated).
|
||||||
|
# example_dsn = "SET sql_select_limit=1000; SET time_zone='+00:00'"
|
||||||
|
|
|
@ -553,3 +553,14 @@ def test_init_command_multiple_arg(executor):
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert expected_sql_select_limit in result.output
|
assert expected_sql_select_limit in result.output
|
||||||
assert expected_max_join_size in result.output
|
assert expected_max_join_size in result.output
|
||||||
|
|
||||||
|
@dbtest
|
||||||
|
def test_global_init_commands(executor):
|
||||||
|
"""Tests that global init-commands from config are executed by default."""
|
||||||
|
# The global init-commands section in test/myclirc sets sql_select_limit=9999
|
||||||
|
sql = 'show variables like "sql_select_limit";'
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, args=CLI_ARGS, input=sql)
|
||||||
|
expected = "sql_select_limit\t9999\n"
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert expected in result.output
|
||||||
|
|
|
@ -173,6 +173,13 @@ def test_favorite_query_expanded_output(executor):
|
||||||
assert_result_equal(results, status="test-ae: Deleted")
|
assert_result_equal(results, status="test-ae: Deleted")
|
||||||
|
|
||||||
|
|
||||||
|
@dbtest
|
||||||
|
def test_collapsed_output_special_command(executor):
|
||||||
|
set_expanded_output(True)
|
||||||
|
run(executor, "select 1\\g")
|
||||||
|
assert is_expanded_output() is False
|
||||||
|
|
||||||
|
|
||||||
@dbtest
|
@dbtest
|
||||||
def test_special_command(executor):
|
def test_special_command(executor):
|
||||||
results = run(executor, "\\?")
|
results = run(executor, "\\?")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue