]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
fix: Update reserved words list of MySQL / MariaDB dialect
authorKevin Kirsche <kevin.kirsche@one.verizon.com>
Tue, 19 Oct 2021 21:22:03 +0000 (17:22 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Oct 2021 16:57:48 +0000 (12:57 -0400)
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 [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/mysql/mariadb.py
lib/sqlalchemy/dialects/mysql/mariadbconnector.py
lib/sqlalchemy/dialects/mysql/mysqldb.py
lib/sqlalchemy/dialects/mysql/reserved_words.py [new file with mode: 0644]
test/dialect/mysql/test_compiler.py
test/dialect/mysql/test_dialect.py

diff --git a/doc/build/changelog/unreleased_14/7167.rst b/doc/build/changelog/unreleased_14/7167.rst
new file mode 100644 (file)
index 0000000..aedc808
--- /dev/null
@@ -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.
index 530e7c9ccb9e33d403caa151d53ab66ec38a0559..ad38fee979e15bd5a04e4ac8e71f87201af2bc77 100644 (file)
@@ -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):
index 8ebde462b885b3179ce0adc8afe36b7491cd13b1..568c3f0cf588923ee1daf307f2527223e0dc903b 100644 (file)
@@ -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):
index 0997abc7abb0f4d8a368d2e65f2a2137c5be5683..14ed11b1999316900ba99149dbf1211bacaee330 100644 (file)
@@ -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
 
index 72a64d3b840e1dbf20ce33222e2488a4cc4481d2..dfe719c28da642af5679266202a3d3de0e34c5f7 100644 (file)
@@ -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 (file)
index 0000000..fc2b13b
--- /dev/null
@@ -0,0 +1,548 @@
+# mysql/reserved_words.py
+# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# 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",
+}
index b43f7de711859b76afc8df5bd68a6598be4e672c..708039f943e302746313fc1ac03f61987d7a8bac 100644 (file)
@@ -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):
index 48fe3d3b1f68d17d76455ea798ade1814f64dd7d..f314bd0af558cd2f7f63e5b32f677a961be57683 100644 (file)
@@ -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