provision.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # dialects/mysql/provision.py
  2. # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. # mypy: ignore-errors
  8. import contextlib
  9. from ... import event
  10. from ... import exc
  11. from ...testing.provision import allow_stale_update_impl
  12. from ...testing.provision import configure_follower
  13. from ...testing.provision import create_db
  14. from ...testing.provision import delete_from_all_tables
  15. from ...testing.provision import drop_db
  16. from ...testing.provision import generate_driver_url
  17. from ...testing.provision import temp_table_keyword_args
  18. from ...testing.provision import upsert
  19. @generate_driver_url.for_db("mysql", "mariadb")
  20. def generate_driver_url(url, driver, query_str):
  21. backend = url.get_backend_name()
  22. # NOTE: at the moment, tests are running mariadbconnector
  23. # against both mariadb and mysql backends. if we want this to be
  24. # limited, do the decision making here to reject a "mysql+mariadbconnector"
  25. # URL. Optionally also re-enable the module level
  26. # MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
  27. # a unit and/or functional test.
  28. # all the Jenkins tests have been running mysqlclient Python library
  29. # built against mariadb client drivers for years against all MySQL /
  30. # MariaDB versions going back to MySQL 5.6, currently they can talk
  31. # to MySQL databases without problems.
  32. if backend == "mysql":
  33. dialect_cls = url.get_dialect()
  34. if dialect_cls._is_mariadb_from_url(url):
  35. backend = "mariadb"
  36. new_url = url.set(
  37. drivername="%s+%s" % (backend, driver)
  38. ).update_query_string(query_str)
  39. if driver == "mariadbconnector":
  40. new_url = new_url.difference_update_query(["charset"])
  41. elif driver == "mysqlconnector":
  42. new_url = new_url.update_query_pairs(
  43. [("collation", "utf8mb4_general_ci")]
  44. )
  45. try:
  46. new_url.get_dialect()
  47. except exc.NoSuchModuleError:
  48. return None
  49. else:
  50. return new_url
  51. @create_db.for_db("mysql", "mariadb")
  52. def _mysql_create_db(cfg, eng, ident):
  53. with eng.begin() as conn:
  54. try:
  55. _mysql_drop_db(cfg, conn, ident)
  56. except Exception:
  57. pass
  58. with eng.begin() as conn:
  59. conn.exec_driver_sql(
  60. "CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
  61. )
  62. conn.exec_driver_sql(
  63. "CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
  64. )
  65. conn.exec_driver_sql(
  66. "CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
  67. )
  68. @configure_follower.for_db("mysql", "mariadb")
  69. def _mysql_configure_follower(config, ident):
  70. config.test_schema = "%s_test_schema" % ident
  71. config.test_schema_2 = "%s_test_schema_2" % ident
  72. @drop_db.for_db("mysql", "mariadb")
  73. def _mysql_drop_db(cfg, eng, ident):
  74. with eng.begin() as conn:
  75. conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
  76. conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
  77. conn.exec_driver_sql("DROP DATABASE %s" % ident)
  78. @temp_table_keyword_args.for_db("mysql", "mariadb")
  79. def _mysql_temp_table_keyword_args(cfg, eng):
  80. return {"prefixes": ["TEMPORARY"]}
  81. @upsert.for_db("mariadb")
  82. def _upsert(
  83. cfg,
  84. table,
  85. returning,
  86. *,
  87. set_lambda=None,
  88. sort_by_parameter_order=False,
  89. index_elements=None,
  90. ):
  91. from sqlalchemy.dialects.mysql import insert
  92. stmt = insert(table)
  93. if set_lambda:
  94. stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
  95. else:
  96. pk1 = table.primary_key.c[0]
  97. stmt = stmt.on_duplicate_key_update({pk1.key: pk1})
  98. stmt = stmt.returning(
  99. *returning, sort_by_parameter_order=sort_by_parameter_order
  100. )
  101. return stmt
  102. @delete_from_all_tables.for_db("mysql", "mariadb")
  103. def _delete_from_all_tables(connection, cfg, metadata):
  104. connection.exec_driver_sql("SET foreign_key_checks = 0")
  105. try:
  106. delete_from_all_tables.call_original(connection, cfg, metadata)
  107. finally:
  108. connection.exec_driver_sql("SET foreign_key_checks = 1")
  109. @allow_stale_update_impl.for_db("mariadb")
  110. def _allow_stale_update_impl(cfg):
  111. @contextlib.contextmanager
  112. def go():
  113. @event.listens_for(cfg.db, "engine_connect")
  114. def turn_off_snapshot_isolation(conn):
  115. conn.exec_driver_sql("SET innodb_snapshot_isolation = 'OFF'")
  116. conn.rollback()
  117. try:
  118. yield
  119. finally:
  120. event.remove(cfg.db, "engine_connect", turn_off_snapshot_isolation)
  121. # dispose the pool; quick way to just have those reset
  122. cfg.db.dispose()
  123. return go()