From ba278a16f3e0c54bb9db8dfb37b3f9b776b7f2e5 Mon Sep 17 00:00:00 2001 From: Kevin Kirsche Date: Tue, 19 Oct 2021 17:22:03 -0400 Subject: [PATCH] fix: Update reserved words list of MySQL / MariaDB dialect Reorganized the list of reserved words into two separate lists, one for MySQL and one for MariaDB, so that these diverging sets of words can be managed more accurately; adjusted the MySQL/MariaDB dialect to switch among these lists based on either explicitly configured or server-version-detected "MySQL" or "MariaDB" backend. Added all current reserved words through MySQL 8 and current MariaDB versions including recently added keywords like "lead" . Pull request courtesy Kevin Kirsche. 1. Move reserved words to it's own file. 2. Add missing reserved words from https://mariadb.com/kb/en/reserved-words/ * Note: this only adds MariaDB though links to MySQL, it also does not include the reserved words for Oracle mode, as listed in the link. Fixes: #7167 Supercedes: #7197 Closes: #7207 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/7207 Pull-request-sha: 5a682e331069520ccad9e6bf3cc5e4a77a889ef0 Change-Id: Ib25be8148568899f56b5c9b42d4f530ade8a04e3 --- doc/build/changelog/unreleased_14/7167.rst | 11 + lib/sqlalchemy/dialects/mysql/base.py | 309 +--------- lib/sqlalchemy/dialects/mysql/mariadb.py | 2 + .../dialects/mysql/mariadbconnector.py | 6 - lib/sqlalchemy/dialects/mysql/mysqldb.py | 6 +- .../dialects/mysql/reserved_words.py | 548 ++++++++++++++++++ test/dialect/mysql/test_compiler.py | 87 ++- test/dialect/mysql/test_dialect.py | 28 +- 8 files changed, 676 insertions(+), 321 deletions(-) create mode 100644 doc/build/changelog/unreleased_14/7167.rst create mode 100644 lib/sqlalchemy/dialects/mysql/reserved_words.py diff --git a/doc/build/changelog/unreleased_14/7167.rst b/doc/build/changelog/unreleased_14/7167.rst new file mode 100644 index 0000000000..aedc8086c0 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7167.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: bug, mysql, mariadb + :tickets: 7167 + + Reorganized the list of reserved words into two separate lists, one for + MySQL and one for MariaDB, so that these diverging sets of words can be + managed more accurately; adjusted the MySQL/MariaDB dialect to switch among + these lists based on either explicitly configured or + server-version-detected "MySQL" or "MariaDB" backend. Added all current + reserved words through MySQL 8 and current MariaDB versions including + recently added keywords like "lead" . Pull request courtesy Kevin Kirsche. diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 530e7c9ccb..ad38fee979 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -983,6 +983,8 @@ from .enumerated import SET from .json import JSON from .json import JSONIndexType from .json import JSONPathType +from .reserved_words import RESERVED_WORDS_MARIADB +from .reserved_words import RESERVED_WORDS_MYSQL from .types import _FloatType from .types import _IntegerType from .types import _MatchType @@ -1037,301 +1039,6 @@ from ...types import DATE from ...types import VARBINARY from ...util import topological - -RESERVED_WORDS = set( - [ - "accessible", - "action", - "add", - "admin", - "all", - "alter", - "analyze", - "and", - "array", # 8.0 - "as", - "asc", - "asensitive", - "before", - "between", - "bigint", - "binary", - "blob", - "both", - "by", - "call", - "cascade", - "case", - "change", - "char", - "character", - "check", - "collate", - "column", - "columns", - "condition", - "constraint", - "continue", - "convert", - "create", - "cross", - "cube", - "cume_dist", - "current_date", - "current_time", - "current_timestamp", - "current_user", - "cursor", - "database", - "databases", - "day_hour", - "day_microsecond", - "day_minute", - "day_second", - "dec", - "decimal", - "declare", - "default", - "delayed", - "delete", - "desc", - "describe", - "deterministic", - "distinct", - "distinctrow", - "div", - "double", - "drop", - "dual", - "each", - "else", - "elseif", - "empty", - "enclosed", - "escaped", - "except", - "exists", - "exit", - "explain", - "false", - "fetch", - "fields", - "first_value", - "float", - "float4", - "float8", - "for", - "force", - "foreign", - "from", - "fulltext", - "function", - "general", - "generated", - "get", - "grant", - "group", - "grouping", - "groups", - "having", - "high_priority", - "hour_microsecond", - "hour_minute", - "hour_second", - "if", - "ignore", - "ignore_server_ids", - "in", - "index", - "infile", - "inner", - "inout", - "insensitive", - "insert", - "int", - "int1", - "int2", - "int3", - "int4", - "int8", - "integer", - "interval", - "into", - "io_after_gtids", - "io_before_gtids", - "is", - "iterate", - "join", - "json_table", - "key", - "keys", - "kill", - "last_value", - "lateral", - "leading", - "leave", - "left", - "level", - "like", - "limit", - "linear", - "linear", - "lines", - "load", - "localtime", - "localtimestamp", - "lock", - "long", - "longblob", - "longtext", - "loop", - "low_priority", - "master_bind", - "master_heartbeat_period", - "master_ssl_verify_server_cert", - "master_ssl_verify_server_cert", - "match", - "maxvalue", - "mediumblob", - "mediumint", - "mediumtext", - "member", # 8.0 - "middleint", - "minute_microsecond", - "minute_second", - "mod", - "mode", - "modifies", - "natural", - "no_write_to_binlog", - "not", - "nth_value", - "ntile", - "null", - "numeric", - "of", - "on", - "one_shot", - "optimize", - "optimizer_costs", - "option", - "optionally", - "or", - "order", - "out", - "outer", - "outfile", - "over", - "partition", - "percent_rank", - "persist", - "persist_only", - "precision", - "primary", - "privileges", - "procedure", - "purge", - "range", - "range", - "rank", - "read", - "read_only", - "read_only", - "read_write", - "read_write", # 5.1 - "reads", - "real", - "recursive", - "references", - "regexp", - "release", - "rename", - "repeat", - "replace", - "require", - "resignal", - "restrict", - "return", - "revoke", - "right", - "rlike", - "role", - "row", - "row_number", - "rows", - "schema", - "schemas", - "second_microsecond", - "select", - "sensitive", - "separator", - "set", - "show", - "signal", - "slow", # 5.5 - "smallint", - "soname", - "spatial", - "specific", - "sql", - "sql_after_gtids", - "sql_before_gtids", # 5.6 - "sql_big_result", - "sql_calc_found_rows", - "sql_small_result", - "sqlexception", - "sqlstate", - "sqlwarning", - "ssl", - "starting", - "status", - "stored", - "straight_join", - "system", - "table", - "tables", # 4.1 - "terminated", - "text", - "then", - "time", - "tinyblob", - "tinyint", - "tinytext", - "to", - "trailing", - "trigger", - "true", - "undo", - "union", - "unique", - "unlock", - "unsigned", - "update", - "usage", - "use", - "using", - "utc_date", - "utc_time", - "utc_timestamp", - "values", - "varbinary", - "varchar", - "varcharacter", - "varying", - "virtual", # 5.7 - "when", - "where", - "while", - "window", # 8.0 - "with", - "write", - "x509", - "xor", - "year_month", - "zerofill", # 5.0 - ] -) - AUTOCOMMIT_RE = re.compile( r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER|LOAD +DATA|REPLACE)", re.I | re.UNICODE, @@ -2586,8 +2293,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler): class MySQLIdentifierPreparer(compiler.IdentifierPreparer): - - reserved_words = RESERVED_WORDS + reserved_words = RESERVED_WORDS_MYSQL def __init__(self, dialect, server_ansiquotes=False, **kw): if not server_ansiquotes: @@ -2605,6 +2311,10 @@ class MySQLIdentifierPreparer(compiler.IdentifierPreparer): return tuple([self.quote_identifier(i) for i in ids if i is not None]) +class MariaDBIdentifierPreparer(MySQLIdentifierPreparer): + reserved_words = RESERVED_WORDS_MARIADB + + @log.class_logger class MySQLDialect(default.DefaultDialect): """Details of the MySQL dialect. @@ -2834,6 +2544,11 @@ class MySQLDialect(default.DefaultDialect): "MySQL version %s is not a MariaDB variant." % (server_version_info,) ) + if is_mariadb: + self.preparer = MariaDBIdentifierPreparer + # this would have been set by the default dialect already, + # so set it again + self.identifier_preparer = self.preparer(self) self.is_mariadb = is_mariadb def do_begin_twophase(self, connection, xid): diff --git a/lib/sqlalchemy/dialects/mysql/mariadb.py b/lib/sqlalchemy/dialects/mysql/mariadb.py index 8ebde462b8..568c3f0cf5 100644 --- a/lib/sqlalchemy/dialects/mysql/mariadb.py +++ b/lib/sqlalchemy/dialects/mysql/mariadb.py @@ -1,3 +1,4 @@ +from .base import MariaDBIdentifierPreparer from .base import MySQLDialect @@ -5,6 +6,7 @@ class MariaDBDialect(MySQLDialect): is_mariadb = True supports_statement_cache = True name = "mariadb" + preparer = MariaDBIdentifierPreparer def loader(driver): diff --git a/lib/sqlalchemy/dialects/mysql/mariadbconnector.py b/lib/sqlalchemy/dialects/mysql/mariadbconnector.py index 0997abc7ab..14ed11b199 100644 --- a/lib/sqlalchemy/dialects/mysql/mariadbconnector.py +++ b/lib/sqlalchemy/dialects/mysql/mariadbconnector.py @@ -32,7 +32,6 @@ import re from .base import MySQLCompiler from .base import MySQLDialect from .base import MySQLExecutionContext -from .base import MySQLIdentifierPreparer from ... import sql from ... import util @@ -51,10 +50,6 @@ class MySQLCompiler_mariadbconnector(MySQLCompiler): pass -class MySQLIdentifierPreparer_mariadbconnector(MySQLIdentifierPreparer): - pass - - class MySQLDialect_mariadbconnector(MySQLDialect): driver = "mariadbconnector" supports_statement_cache = True @@ -78,7 +73,6 @@ class MySQLDialect_mariadbconnector(MySQLDialect): default_paramstyle = "qmark" execution_ctx_cls = MySQLExecutionContext_mariadbconnector statement_compiler = MySQLCompiler_mariadbconnector - preparer = MySQLIdentifierPreparer_mariadbconnector supports_server_side_cursors = True diff --git a/lib/sqlalchemy/dialects/mysql/mysqldb.py b/lib/sqlalchemy/dialects/mysql/mysqldb.py index 72a64d3b84..dfe719c28d 100644 --- a/lib/sqlalchemy/dialects/mysql/mysqldb.py +++ b/lib/sqlalchemy/dialects/mysql/mysqldb.py @@ -120,10 +120,6 @@ class MySQLCompiler_mysqldb(MySQLCompiler): pass -class MySQLIdentifierPreparer_mysqldb(MySQLIdentifierPreparer): - pass - - class MySQLDialect_mysqldb(MySQLDialect): driver = "mysqldb" supports_statement_cache = True @@ -136,7 +132,7 @@ class MySQLDialect_mysqldb(MySQLDialect): default_paramstyle = "format" execution_ctx_cls = MySQLExecutionContext_mysqldb statement_compiler = MySQLCompiler_mysqldb - preparer = MySQLIdentifierPreparer_mysqldb + preparer = MySQLIdentifierPreparer def __init__(self, **kwargs): super(MySQLDialect_mysqldb, self).__init__(**kwargs) diff --git a/lib/sqlalchemy/dialects/mysql/reserved_words.py b/lib/sqlalchemy/dialects/mysql/reserved_words.py new file mode 100644 index 0000000000..fc2b13b44f --- /dev/null +++ b/lib/sqlalchemy/dialects/mysql/reserved_words.py @@ -0,0 +1,548 @@ +# mysql/reserved_words.py +# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + +# generated using: +# https://gist.github.com/kkirsche/4f31f2153ed7a3248be1ec44ca6ddbc9 +# +# https://mariadb.com/kb/en/reserved-words/ +# includes: Reserved Words +# excludes: Exceptions, Oracle Mode, Function Names +RESERVED_WORDS_MARIADB = { + "accessible", + "add", + "all", + "alter", + "analyze", + "and", + "as", + "asc", + "asensitive", + "before", + "between", + "bigint", + "binary", + "blob", + "both", + "by", + "call", + "cascade", + "case", + "change", + "char", + "character", + "check", + "collate", + "column", + "condition", + "constraint", + "continue", + "convert", + "create", + "cross", + "current_date", + "current_role", + "current_time", + "current_timestamp", + "current_user", + "cursor", + "database", + "databases", + "day_hour", + "day_microsecond", + "day_minute", + "day_second", + "dec", + "decimal", + "declare", + "default", + "delayed", + "delete", + "desc", + "describe", + "deterministic", + "distinct", + "distinctrow", + "div", + "do_domain_ids", + "double", + "drop", + "dual", + "each", + "else", + "elseif", + "enclosed", + "escaped", + "except", + "exists", + "exit", + "explain", + "false", + "fetch", + "float", + "float4", + "float8", + "for", + "force", + "foreign", + "from", + "fulltext", + "general", + "grant", + "group", + "having", + "high_priority", + "hour_microsecond", + "hour_minute", + "hour_second", + "if", + "ignore", + "ignore_domain_ids", + "ignore_server_ids", + "in", + "index", + "infile", + "inner", + "inout", + "insensitive", + "insert", + "int", + "int1", + "int2", + "int3", + "int4", + "int8", + "integer", + "intersect", + "interval", + "into", + "is", + "iterate", + "join", + "key", + "keys", + "kill", + "leading", + "leave", + "left", + "like", + "limit", + "linear", + "lines", + "load", + "localtime", + "localtimestamp", + "lock", + "long", + "longblob", + "longtext", + "loop", + "low_priority", + "master_heartbeat_period", + "master_ssl_verify_server_cert", + "match", + "maxvalue", + "mediumblob", + "mediumint", + "mediumtext", + "middleint", + "minute_microsecond", + "minute_second", + "mod", + "modifies", + "natural", + "no_write_to_binlog", + "not", + "null", + "numeric", + "offset", + "on", + "optimize", + "option", + "optionally", + "or", + "order", + "out", + "outer", + "outfile", + "over", + "page_checksum", + "parse_vcol_expr", + "partition", + "position", + "precision", + "primary", + "procedure", + "purge", + "range", + "read", + "read_write", + "reads", + "real", + "recursive", + "ref_system_id", + "references", + "regexp", + "release", + "rename", + "repeat", + "replace", + "require", + "resignal", + "restrict", + "return", + "returning", + "revoke", + "right", + "rlike", + "rows", + "schema", + "schemas", + "second_microsecond", + "select", + "sensitive", + "separator", + "set", + "show", + "signal", + "slow", + "smallint", + "spatial", + "specific", + "sql", + "sql_big_result", + "sql_calc_found_rows", + "sql_small_result", + "sqlexception", + "sqlstate", + "sqlwarning", + "ssl", + "starting", + "stats_auto_recalc", + "stats_persistent", + "stats_sample_pages", + "straight_join", + "table", + "terminated", + "then", + "tinyblob", + "tinyint", + "tinytext", + "to", + "trailing", + "trigger", + "true", + "undo", + "union", + "unique", + "unlock", + "unsigned", + "update", + "usage", + "use", + "using", + "utc_date", + "utc_time", + "utc_timestamp", + "values", + "varbinary", + "varchar", + "varcharacter", + "varying", + "when", + "where", + "while", + "window", + "with", + "write", + "xor", + "year_month", + "zerofill", +} + +# https://dev.mysql.com/doc/refman/8.0/en/keywords.html +# https://dev.mysql.com/doc/refman/5.7/en/keywords.html +# https://dev.mysql.com/doc/refman/5.6/en/keywords.html +# includes: MySQL x.0 Keywords and Reserved Words +# excludes: MySQL x.0 New Keywords and Reserved Words, +# MySQL x.0 Removed Keywords and Reserved Words +RESERVED_WORDS_MYSQL = { + "accessible", + "add", + "admin", + "all", + "alter", + "analyze", + "and", + "array", + "as", + "asc", + "asensitive", + "before", + "between", + "bigint", + "binary", + "blob", + "both", + "by", + "call", + "cascade", + "case", + "change", + "char", + "character", + "check", + "collate", + "column", + "condition", + "constraint", + "continue", + "convert", + "create", + "cross", + "cube", + "cume_dist", + "current_date", + "current_time", + "current_timestamp", + "current_user", + "cursor", + "database", + "databases", + "day_hour", + "day_microsecond", + "day_minute", + "day_second", + "dec", + "decimal", + "declare", + "default", + "delayed", + "delete", + "dense_rank", + "desc", + "describe", + "deterministic", + "distinct", + "distinctrow", + "div", + "double", + "drop", + "dual", + "each", + "else", + "elseif", + "empty", + "enclosed", + "escaped", + "except", + "exists", + "exit", + "explain", + "false", + "fetch", + "first_value", + "float", + "float4", + "float8", + "for", + "force", + "foreign", + "from", + "fulltext", + "function", + "general", + "generated", + "get", + "get_master_public_key", + "grant", + "group", + "grouping", + "groups", + "having", + "high_priority", + "hour_microsecond", + "hour_minute", + "hour_second", + "if", + "ignore", + "ignore_server_ids", + "in", + "index", + "infile", + "inner", + "inout", + "insensitive", + "insert", + "int", + "int1", + "int2", + "int3", + "int4", + "int8", + "integer", + "interval", + "into", + "io_after_gtids", + "io_before_gtids", + "is", + "iterate", + "join", + "json_table", + "key", + "keys", + "kill", + "lag", + "last_value", + "lateral", + "lead", + "leading", + "leave", + "left", + "like", + "limit", + "linear", + "lines", + "load", + "localtime", + "localtimestamp", + "lock", + "long", + "longblob", + "longtext", + "loop", + "low_priority", + "master_bind", + "master_heartbeat_period", + "master_ssl_verify_server_cert", + "match", + "maxvalue", + "mediumblob", + "mediumint", + "mediumtext", + "member", + "middleint", + "minute_microsecond", + "minute_second", + "mod", + "modifies", + "natural", + "no_write_to_binlog", + "not", + "nth_value", + "ntile", + "null", + "numeric", + "of", + "on", + "optimize", + "optimizer_costs", + "option", + "optionally", + "or", + "order", + "out", + "outer", + "outfile", + "over", + "parse_gcol_expr", + "partition", + "percent_rank", + "persist", + "persist_only", + "precision", + "primary", + "procedure", + "purge", + "range", + "rank", + "read", + "read_write", + "reads", + "real", + "recursive", + "references", + "regexp", + "release", + "rename", + "repeat", + "replace", + "require", + "resignal", + "restrict", + "return", + "revoke", + "right", + "rlike", + "role", + "row", + "row_number", + "rows", + "schema", + "schemas", + "second_microsecond", + "select", + "sensitive", + "separator", + "set", + "show", + "signal", + "slow", + "smallint", + "spatial", + "specific", + "sql", + "sql_after_gtids", + "sql_before_gtids", + "sql_big_result", + "sql_calc_found_rows", + "sql_small_result", + "sqlexception", + "sqlstate", + "sqlwarning", + "ssl", + "starting", + "stored", + "straight_join", + "system", + "table", + "terminated", + "then", + "tinyblob", + "tinyint", + "tinytext", + "to", + "trailing", + "trigger", + "true", + "undo", + "union", + "unique", + "unlock", + "unsigned", + "update", + "usage", + "use", + "using", + "utc_date", + "utc_time", + "utc_timestamp", + "values", + "varbinary", + "varchar", + "varcharacter", + "varying", + "virtual", + "when", + "where", + "while", + "window", + "with", + "write", + "xor", + "year_month", + "zerofill", +} diff --git a/test/dialect/mysql/test_compiler.py b/test/dialect/mysql/test_compiler.py index b43f7de711..708039f943 100644 --- a/test/dialect/mysql/test_compiler.py +++ b/test/dialect/mysql/test_compiler.py @@ -9,6 +9,7 @@ from sqlalchemy import CheckConstraint from sqlalchemy import CLOB from sqlalchemy import Column from sqlalchemy import Computed +from sqlalchemy import create_engine from sqlalchemy import DATE from sqlalchemy import Date from sqlalchemy import DATETIME @@ -60,25 +61,87 @@ from sqlalchemy.testing import AssertsCompiledSQL from sqlalchemy.testing import eq_ from sqlalchemy.testing import expect_warnings from sqlalchemy.testing import fixtures +from sqlalchemy.testing import mock -class CompileTest(fixtures.TestBase, AssertsCompiledSQL): - - __dialect__ = mysql.dialect() - - def test_reserved_words(self): +class ReservedWordFixture(AssertsCompiledSQL): + @testing.fixture() + def mysql_mariadb_reserved_words(self): table = Table( - "mysql_table", + "rw_table", MetaData(), - Column("col1", Integer), - Column("master_ssl_verify_server_cert", Integer), + Column("mysql_reserved", Integer), + Column("mdb_mysql_reserved", Integer), + Column("mdb_reserved", Integer), ) - x = select(table.c.col1, table.c.master_ssl_verify_server_cert) + expected_mysql = ( + "SELECT rw_table.`mysql_reserved`, " + "rw_table.`mdb_mysql_reserved`, " + "rw_table.mdb_reserved FROM rw_table" + ) + expected_mdb = ( + "SELECT rw_table.mysql_reserved, " + "rw_table.`mdb_mysql_reserved`, " + "rw_table.`mdb_reserved` FROM rw_table" + ) + + from sqlalchemy.dialects.mysql import reserved_words + + reserved_words.RESERVED_WORDS_MARIADB.add("mdb_reserved") + reserved_words.RESERVED_WORDS_MYSQL.add("mysql_reserved") + reserved_words.RESERVED_WORDS_MYSQL.add("mdb_mysql_reserved") + reserved_words.RESERVED_WORDS_MARIADB.add("mdb_mysql_reserved") + + try: + yield table, expected_mysql, expected_mdb + finally: + + reserved_words.RESERVED_WORDS_MARIADB.discard("mdb_reserved") + reserved_words.RESERVED_WORDS_MYSQL.discard("mysql_reserved") + reserved_words.RESERVED_WORDS_MYSQL.discard("mdb_mysql_reserved") + reserved_words.RESERVED_WORDS_MARIADB.discard("mdb_mysql_reserved") + + +class CompileTest(ReservedWordFixture, fixtures.TestBase, AssertsCompiledSQL): + + __dialect__ = mysql.dialect() + + @testing.combinations( + ("mariadb", True), + ("mysql", False), + (mysql.dialect(), False), + (mysql.dialect(is_mariadb=True), True), + ( + create_engine( + "mysql+pymysql://", module=mock.Mock(paramstyle="format") + ).dialect, + False, + ), + ( + create_engine( + "mariadb+pymysql://", module=mock.Mock(paramstyle="format") + ).dialect, + True, + ), + argnames="dialect, expect_mariadb", + ) + def test_reserved_words_mysql_vs_mariadb( + self, dialect, expect_mariadb, mysql_mariadb_reserved_words + ): + """test #7167 - compiler level + + We want to make sure that the "is mariadb" flag as well as the + correct identifier preparer are set up for dialects no matter how they + determine their "is_mariadb" flag. + + """ + + table, expected_mysql, expected_mdb = mysql_mariadb_reserved_words self.assert_compile( - x, - "SELECT mysql_table.col1, " - "mysql_table.`master_ssl_verify_server_cert` FROM mysql_table", + select(table), + expected_mdb if expect_mariadb else expected_mysql, + dialect=dialect, ) def test_create_index_simple(self): diff --git a/test/dialect/mysql/test_dialect.py b/test/dialect/mysql/test_dialect.py index 48fe3d3b1f..f314bd0af5 100644 --- a/test/dialect/mysql/test_dialect.py +++ b/test/dialect/mysql/test_dialect.py @@ -9,6 +9,7 @@ from sqlalchemy import exc from sqlalchemy import func from sqlalchemy import Integer from sqlalchemy import MetaData +from sqlalchemy import select from sqlalchemy import Table from sqlalchemy import testing from sqlalchemy.dialects import mysql @@ -21,13 +22,38 @@ from sqlalchemy.testing import fixtures from sqlalchemy.testing import in_ from sqlalchemy.testing import is_ from sqlalchemy.testing import mock +from sqlalchemy.testing.assertions import AssertsCompiledSQL +from .test_compiler import ReservedWordFixture from ...engine import test_deprecations -class BackendDialectTest(fixtures.TestBase): +class BackendDialectTest( + ReservedWordFixture, fixtures.TestBase, AssertsCompiledSQL +): __backend__ = True __only_on__ = "mysql", "mariadb" + def test_reserved_words_mysql_vs_mariadb( + self, mysql_mariadb_reserved_words + ): + """test #7167 - real backend level + + We want to make sure that the "is mariadb" flag as well as the + correct identifier preparer are set up for dialects no matter how they + determine their "is_mariadb" flag. + + """ + + dialect = testing.db.dialect + expect_mariadb = testing.only_on("mariadb").enabled + + table, expected_mysql, expected_mdb = mysql_mariadb_reserved_words + self.assert_compile( + select(table), + expected_mdb if expect_mariadb else expected_mysql, + dialect=dialect, + ) + def test_no_show_variables(self): from sqlalchemy.testing import mock -- 2.47.3