From d8d94f97f0c4c6c676a3a005a27f2765f2f85751 Mon Sep 17 00:00:00 2001 From: Georgy Frolov Date: Tue, 28 Apr 2020 11:30:35 +0300 Subject: [PATCH 1/3] Fix binary data representation in SQL formatters --- mycli/packages/tabular_output/sql_format.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mycli/packages/tabular_output/sql_format.py b/mycli/packages/tabular_output/sql_format.py index 3ad0aa2a..cf0ce5a7 100644 --- a/mycli/packages/tabular_output/sql_format.py +++ b/mycli/packages/tabular_output/sql_format.py @@ -9,6 +9,13 @@ preprocessors = () +def escape_for_sql_statement(value): + if isinstance(value, bytes): + return f"X'{value.hex()}'" + else: + return formatter.mycli.sqlexecute.conn.escape(value) + + def adapter(data, headers, table_format=None, **kwargs): tables = extract_tables(formatter.query) if len(tables) > 0: @@ -19,13 +26,12 @@ def adapter(data, headers, table_format=None, **kwargs): table_name = table[1] else: table_name = "`DUAL`" - escape = formatter.mycli.sqlexecute.conn.escape if table_format == 'sql-insert': h = "`, `".join(headers) yield "INSERT INTO {} (`{}`) VALUES".format(table_name, h) prefix = " " for d in data: - values = ", ".join(escape(v) for i, v in enumerate(d)) + values = ", ".join(escape_for_sql_statement(v) for i, v in enumerate(d)) yield "{}({})".format(prefix, values) if prefix == " ": prefix = ", " @@ -39,11 +45,11 @@ def adapter(data, headers, table_format=None, **kwargs): yield "UPDATE {} SET".format(table_name) prefix = " " for i, v in enumerate(d[keys:], keys): - yield "{}`{}` = {}".format(prefix, headers[i], escape(v)) + yield "{}`{}` = {}".format(prefix, headers[i], escape_for_sql_statement(v)) if prefix == " ": prefix = ", " f = "`{}` = {}" - where = (f.format(headers[i], escape(d[i])) for i in range(keys)) + where = (f.format(headers[i], escape_for_sql_statement(d[i])) for i in range(keys)) yield "WHERE {};".format(" AND ".join(where)) From eee98f25117c9b54ac36023f29830af44b46c973 Mon Sep 17 00:00:00 2001 From: Georgy Frolov Date: Sun, 3 May 2020 11:56:12 +0300 Subject: [PATCH 2/3] update tests for sql output --- test/test_tabular_output.py | 40 +++++++++++++++++++++++-------------- test/utils.py | 2 +- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/test/test_tabular_output.py b/test/test_tabular_output.py index 501198f3..fe7e5da3 100644 --- a/test/test_tabular_output.py +++ b/test/test_tabular_output.py @@ -23,13 +23,21 @@ def mycli(): @dbtest def test_sql_output(mycli): """Test the sql output adapter.""" - headers = ['letters', 'number', 'optional', 'float'] + headers = ['letters', 'number', 'optional', 'float', 'binary'] class FakeCursor(object): def __init__(self): - self.data = [('abc', 1, None, 10.0), ('d', 456, '1', 0.5)] - self.description = [(None, FIELD_TYPE.VARCHAR), (None, FIELD_TYPE.LONG), - (None, FIELD_TYPE.LONG), (None, FIELD_TYPE.FLOAT)] + self.data = [ + ('abc', 1, None, 10.0, b'\xAA'), + ('d', 456, '1', 0.5, b'\xAA\xBB') + ] + self.description = [ + (None, FIELD_TYPE.VARCHAR), + (None, FIELD_TYPE.LONG), + (None, FIELD_TYPE.LONG), + (None, FIELD_TYPE.FLOAT), + (None, FIELD_TYPE.BLOB) + ] def __iter__(self): return self @@ -40,8 +48,6 @@ def __next__(self): else: raise StopIteration() - next = __next__ # Python 2 - def description(self): return self.description @@ -55,11 +61,13 @@ def description(self): `number` = 1 , `optional` = NULL , `float` = 10 + , `binary` = X'aa' WHERE `letters` = 'abc'; UPDATE `DUAL` SET `number` = 456 , `optional` = '1' , `float` = 0.5 + , `binary` = X'aabb' WHERE `letters` = 'd';''') # Test sql-update-2 output format assert list(mycli.change_table_format("sql-update-2")) == \ @@ -70,10 +78,12 @@ def description(self): UPDATE `DUAL` SET `optional` = NULL , `float` = 10 + , `binary` = X'aa' WHERE `letters` = 'abc' AND `number` = 1; UPDATE `DUAL` SET `optional` = '1' , `float` = 0.5 + , `binary` = X'aabb' WHERE `letters` = 'd' AND `number` = 456;''') # Test sql-insert output format (without table name) assert list(mycli.change_table_format("sql-insert")) == \ @@ -81,9 +91,9 @@ def description(self): mycli.formatter.query = "" output = mycli.format_output(None, FakeCursor(), headers) assert "\n".join(output) == dedent('''\ - INSERT INTO `DUAL` (`letters`, `number`, `optional`, `float`) VALUES - ('abc', 1, NULL, 10) - , ('d', 456, '1', 0.5) + INSERT INTO `DUAL` (`letters`, `number`, `optional`, `float`, `binary`) VALUES + ('abc', 1, NULL, 10, X'aa') + , ('d', 456, '1', 0.5, X'aabb') ;''') # Test sql-insert output format (with table name) assert list(mycli.change_table_format("sql-insert")) == \ @@ -91,9 +101,9 @@ def description(self): mycli.formatter.query = "SELECT * FROM `table`" output = mycli.format_output(None, FakeCursor(), headers) assert "\n".join(output) == dedent('''\ - INSERT INTO `table` (`letters`, `number`, `optional`, `float`) VALUES - ('abc', 1, NULL, 10) - , ('d', 456, '1', 0.5) + INSERT INTO `table` (`letters`, `number`, `optional`, `float`, `binary`) VALUES + ('abc', 1, NULL, 10, X'aa') + , ('d', 456, '1', 0.5, X'aabb') ;''') # Test sql-insert output format (with database + table name) assert list(mycli.change_table_format("sql-insert")) == \ @@ -101,7 +111,7 @@ def description(self): mycli.formatter.query = "SELECT * FROM `database`.`table`" output = mycli.format_output(None, FakeCursor(), headers) assert "\n".join(output) == dedent('''\ - INSERT INTO `database`.`table` (`letters`, `number`, `optional`, `float`) VALUES - ('abc', 1, NULL, 10) - , ('d', 456, '1', 0.5) + INSERT INTO `database`.`table` (`letters`, `number`, `optional`, `float`, `binary`) VALUES + ('abc', 1, NULL, 10, X'aa') + , ('d', 456, '1', 0.5, X'aabb') ;''') diff --git a/test/utils.py b/test/utils.py index 57669658..66b41940 100644 --- a/test/utils.py +++ b/test/utils.py @@ -12,7 +12,7 @@ PASSWORD = os.getenv('PYTEST_PASSWORD') USER = os.getenv('PYTEST_USER', 'root') HOST = os.getenv('PYTEST_HOST', 'localhost') -PORT = os.getenv('PYTEST_PORT', 3306) +PORT = int(os.getenv('PYTEST_PORT', 3306)) CHARSET = os.getenv('PYTEST_CHARSET', 'utf8') SSH_USER = os.getenv('PYTEST_SSH_USER', None) SSH_HOST = os.getenv('PYTEST_SSH_HOST', None) From e62231169df7add02b0d6776ab575668a7aced08 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Sun, 3 May 2020 21:17:04 -0700 Subject: [PATCH 3/3] Lint fixes. --- mycli/packages/tabular_output/sql_format.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mycli/packages/tabular_output/sql_format.py b/mycli/packages/tabular_output/sql_format.py index cf0ce5a7..730e6332 100644 --- a/mycli/packages/tabular_output/sql_format.py +++ b/mycli/packages/tabular_output/sql_format.py @@ -31,7 +31,8 @@ def adapter(data, headers, table_format=None, **kwargs): yield "INSERT INTO {} (`{}`) VALUES".format(table_name, h) prefix = " " for d in data: - values = ", ".join(escape_for_sql_statement(v) for i, v in enumerate(d)) + values = ", ".join(escape_for_sql_statement(v) + for i, v in enumerate(d)) yield "{}({})".format(prefix, values) if prefix == " ": prefix = ", " @@ -49,7 +50,8 @@ def adapter(data, headers, table_format=None, **kwargs): if prefix == " ": prefix = ", " f = "`{}` = {}" - where = (f.format(headers[i], escape_for_sql_statement(d[i])) for i in range(keys)) + where = (f.format(headers[i], escape_for_sql_statement( + d[i])) for i in range(keys)) yield "WHERE {};".format(" AND ".join(where))