base.py 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079
  1. # mysql/base.py
  2. # Copyright (C) 2005-2017 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: http://www.opensource.org/licenses/mit-license.php
  7. r"""
  8. .. dialect:: mysql
  9. :name: MySQL
  10. Supported Versions and Features
  11. -------------------------------
  12. SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
  13. However, no heroic measures are taken to work around major missing
  14. SQL features - if your server version does not support sub-selects, for
  15. example, they won't work in SQLAlchemy either.
  16. See the official MySQL documentation for detailed information about features
  17. supported in any given server release.
  18. .. _mysql_connection_timeouts:
  19. Connection Timeouts
  20. -------------------
  21. MySQL features an automatic connection close behavior, for connections that
  22. have been idle for eight hours or more. To circumvent having this issue, use
  23. the ``pool_recycle`` option which controls the maximum age of any connection::
  24. engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
  25. .. seealso::
  26. :ref:`pool_setting_recycle` - full description of the pool recycle feature.
  27. .. _mysql_storage_engines:
  28. CREATE TABLE arguments including Storage Engines
  29. ------------------------------------------------
  30. MySQL's CREATE TABLE syntax includes a wide array of special options,
  31. including ``ENGINE``, ``CHARSET``, ``MAX_ROWS``, ``ROW_FORMAT``,
  32. ``INSERT_METHOD``, and many more.
  33. To accommodate the rendering of these arguments, specify the form
  34. ``mysql_argument_name="value"``. For example, to specify a table with
  35. ``ENGINE`` of ``InnoDB``, ``CHARSET`` of ``utf8``, and ``KEY_BLOCK_SIZE``
  36. of ``1024``::
  37. Table('mytable', metadata,
  38. Column('data', String(32)),
  39. mysql_engine='InnoDB',
  40. mysql_charset='utf8',
  41. mysql_key_block_size="1024"
  42. )
  43. The MySQL dialect will normally transfer any keyword specified as
  44. ``mysql_keyword_name`` to be rendered as ``KEYWORD_NAME`` in the
  45. ``CREATE TABLE`` statement. A handful of these names will render with a space
  46. instead of an underscore; to support this, the MySQL dialect has awareness of
  47. these particular names, which include ``DATA DIRECTORY``
  48. (e.g. ``mysql_data_directory``), ``CHARACTER SET`` (e.g.
  49. ``mysql_character_set``) and ``INDEX DIRECTORY`` (e.g.
  50. ``mysql_index_directory``).
  51. The most common argument is ``mysql_engine``, which refers to the storage
  52. engine for the table. Historically, MySQL server installations would default
  53. to ``MyISAM`` for this value, although newer versions may be defaulting
  54. to ``InnoDB``. The ``InnoDB`` engine is typically preferred for its support
  55. of transactions and foreign keys.
  56. A :class:`.Table` that is created in a MySQL database with a storage engine
  57. of ``MyISAM`` will be essentially non-transactional, meaning any
  58. INSERT/UPDATE/DELETE statement referring to this table will be invoked as
  59. autocommit. It also will have no support for foreign key constraints; while
  60. the ``CREATE TABLE`` statement accepts foreign key options, when using the
  61. ``MyISAM`` storage engine these arguments are discarded. Reflecting such a
  62. table will also produce no foreign key constraint information.
  63. For fully atomic transactions as well as support for foreign key
  64. constraints, all participating ``CREATE TABLE`` statements must specify a
  65. transactional engine, which in the vast majority of cases is ``InnoDB``.
  66. .. seealso::
  67. `The InnoDB Storage Engine
  68. <http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html>`_ -
  69. on the MySQL website.
  70. Case Sensitivity and Table Reflection
  71. -------------------------------------
  72. MySQL has inconsistent support for case-sensitive identifier
  73. names, basing support on specific details of the underlying
  74. operating system. However, it has been observed that no matter
  75. what case sensitivity behavior is present, the names of tables in
  76. foreign key declarations are *always* received from the database
  77. as all-lower case, making it impossible to accurately reflect a
  78. schema where inter-related tables use mixed-case identifier names.
  79. Therefore it is strongly advised that table names be declared as
  80. all lower case both within SQLAlchemy as well as on the MySQL
  81. database itself, especially if database reflection features are
  82. to be used.
  83. .. _mysql_isolation_level:
  84. Transaction Isolation Level
  85. ---------------------------
  86. All MySQL dialects support setting of transaction isolation level
  87. both via a dialect-specific parameter :paramref:`.create_engine.isolation_level`
  88. accepted by :func:`.create_engine`,
  89. as well as the :paramref:`.Connection.execution_options.isolation_level`
  90. argument as passed to :meth:`.Connection.execution_options`.
  91. This feature works by issuing the command
  92. ``SET SESSION TRANSACTION ISOLATION LEVEL <level>`` for
  93. each new connection. For the special AUTOCOMMIT isolation level, DBAPI-specific
  94. techniques are used.
  95. To set isolation level using :func:`.create_engine`::
  96. engine = create_engine(
  97. "mysql://scott:tiger@localhost/test",
  98. isolation_level="READ UNCOMMITTED"
  99. )
  100. To set using per-connection execution options::
  101. connection = engine.connect()
  102. connection = connection.execution_options(
  103. isolation_level="READ COMMITTED"
  104. )
  105. Valid values for ``isolation_level`` include:
  106. * ``READ COMMITTED``
  107. * ``READ UNCOMMITTED``
  108. * ``REPEATABLE READ``
  109. * ``SERIALIZABLE``
  110. * ``AUTOCOMMIT``
  111. The special ``AUTOCOMMIT`` value makes use of the various "autocommit"
  112. attributes provided by specific DBAPIs, and is currently supported by
  113. MySQLdb, MySQL-Client, MySQL-Connector Python, and PyMySQL. Using it,
  114. the MySQL connection will return true for the value of
  115. ``SELECT @@autocommit;``.
  116. .. versionadded:: 1.1 - added support for the AUTOCOMMIT isolation level.
  117. AUTO_INCREMENT Behavior
  118. -----------------------
  119. When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
  120. the first :class:`.Integer` primary key column which is not marked as a
  121. foreign key::
  122. >>> t = Table('mytable', metadata,
  123. ... Column('mytable_id', Integer, primary_key=True)
  124. ... )
  125. >>> t.create()
  126. CREATE TABLE mytable (
  127. id INTEGER NOT NULL AUTO_INCREMENT,
  128. PRIMARY KEY (id)
  129. )
  130. You can disable this behavior by passing ``False`` to the
  131. :paramref:`~.Column.autoincrement` argument of :class:`.Column`. This flag
  132. can also be used to enable auto-increment on a secondary column in a
  133. multi-column key for some storage engines::
  134. Table('mytable', metadata,
  135. Column('gid', Integer, primary_key=True, autoincrement=False),
  136. Column('id', Integer, primary_key=True)
  137. )
  138. .. _mysql_ss_cursors:
  139. Server Side Cursors
  140. -------------------
  141. Server-side cursor support is available for the MySQLdb and PyMySQL dialects.
  142. From a MySQL point of view this means that the ``MySQLdb.cursors.SSCursor`` or
  143. ``pymysql.cursors.SSCursor`` class is used when building up the cursor which
  144. will receive results. The most typical way of invoking this feature is via the
  145. :paramref:`.Connection.execution_options.stream_results` connection execution
  146. option. Server side cursors can also be enabled for all SELECT statements
  147. unconditionally by passing ``server_side_cursors=True`` to
  148. :func:`.create_engine`.
  149. .. versionadded:: 1.1.4 - added server-side cursor support.
  150. .. _mysql_unicode:
  151. Unicode
  152. -------
  153. Charset Selection
  154. ~~~~~~~~~~~~~~~~~
  155. Most MySQL DBAPIs offer the option to set the client character set for
  156. a connection. This is typically delivered using the ``charset`` parameter
  157. in the URL, such as::
  158. e = create_engine(
  159. "mysql+pymysql://scott:tiger@localhost/test?charset=utf8")
  160. This charset is the **client character set** for the connection. Some
  161. MySQL DBAPIs will default this to a value such as ``latin1``, and some
  162. will make use of the ``default-character-set`` setting in the ``my.cnf``
  163. file as well. Documentation for the DBAPI in use should be consulted
  164. for specific behavior.
  165. The encoding used for Unicode has traditionally been ``'utf8'``. However,
  166. for MySQL versions 5.5.3 on forward, a new MySQL-specific encoding
  167. ``'utf8mb4'`` has been introduced. The rationale for this new encoding
  168. is due to the fact that MySQL's utf-8 encoding only supports
  169. codepoints up to three bytes instead of four. Therefore,
  170. when communicating with a MySQL database
  171. that includes codepoints more than three bytes in size,
  172. this new charset is preferred, if supported by both the database as well
  173. as the client DBAPI, as in::
  174. e = create_engine(
  175. "mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4")
  176. At the moment, up-to-date versions of MySQLdb and PyMySQL support the
  177. ``utf8mb4`` charset. Other DBAPIs such as MySQL-Connector and OurSQL
  178. may **not** support it as of yet.
  179. In order to use ``utf8mb4`` encoding, changes to
  180. the MySQL schema and/or server configuration may be required.
  181. .. seealso::
  182. `The utf8mb4 Character Set \
  183. <http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html>`_ - \
  184. in the MySQL documentation
  185. Unicode Encoding / Decoding
  186. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  187. All modern MySQL DBAPIs all offer the service of handling the encoding and
  188. decoding of unicode data between the Python application space and the database.
  189. As this was not always the case, SQLAlchemy also includes a comprehensive system
  190. of performing the encode/decode task as well. As only one of these systems
  191. should be in use at at time, SQLAlchemy has long included functionality
  192. to automatically detect upon first connection whether or not the DBAPI is
  193. automatically handling unicode.
  194. Whether or not the MySQL DBAPI will handle encoding can usually be configured
  195. using a DBAPI flag ``use_unicode``, which is known to be supported at least
  196. by MySQLdb, PyMySQL, and MySQL-Connector. Setting this value to ``0``
  197. in the "connect args" or query string will have the effect of disabling the
  198. DBAPI's handling of unicode, such that it instead will return data of the
  199. ``str`` type or ``bytes`` type, with data in the configured charset::
  200. # connect while disabling the DBAPI's unicode encoding/decoding
  201. e = create_engine(
  202. "mysql+mysqldb://scott:tiger@localhost/test?charset=utf8&use_unicode=0")
  203. Current recommendations for modern DBAPIs are as follows:
  204. * It is generally always safe to leave the ``use_unicode`` flag set at
  205. its default; that is, don't use it at all.
  206. * Under Python 3, the ``use_unicode=0`` flag should **never be used**.
  207. SQLAlchemy under Python 3 generally assumes the DBAPI receives and returns
  208. string values as Python 3 strings, which are inherently unicode objects.
  209. * Under Python 2 with MySQLdb, the ``use_unicode=0`` flag will **offer
  210. superior performance**, as MySQLdb's unicode converters under Python 2 only
  211. have been observed to have unusually slow performance compared to SQLAlchemy's
  212. fast C-based encoders/decoders.
  213. In short: don't specify ``use_unicode`` *at all*, with the possible
  214. exception of ``use_unicode=0`` on MySQLdb with Python 2 **only** for a
  215. potential performance gain.
  216. Ansi Quoting Style
  217. ------------------
  218. MySQL features two varieties of identifier "quoting style", one using
  219. backticks and the other using quotes, e.g. ```some_identifier``` vs.
  220. ``"some_identifier"``. All MySQL dialects detect which version
  221. is in use by checking the value of ``sql_mode`` when a connection is first
  222. established with a particular :class:`.Engine`. This quoting style comes
  223. into play when rendering table and column names as well as when reflecting
  224. existing database structures. The detection is entirely automatic and
  225. no special configuration is needed to use either quoting style.
  226. .. versionchanged:: 0.6 detection of ANSI quoting style is entirely automatic,
  227. there's no longer any end-user ``create_engine()`` options in this regard.
  228. MySQL SQL Extensions
  229. --------------------
  230. Many of the MySQL SQL extensions are handled through SQLAlchemy's generic
  231. function and operator support::
  232. table.select(table.c.password==func.md5('plaintext'))
  233. table.select(table.c.username.op('regexp')('^[a-d]'))
  234. And of course any valid MySQL statement can be executed as a string as well.
  235. Some limited direct support for MySQL extensions to SQL is currently
  236. available.
  237. * SELECT pragma::
  238. select(..., prefixes=['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
  239. * UPDATE with LIMIT::
  240. update(..., mysql_limit=10)
  241. rowcount Support
  242. ----------------
  243. SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
  244. usual definition of "number of rows matched by an UPDATE or DELETE" statement.
  245. This is in contradiction to the default setting on most MySQL DBAPI drivers,
  246. which is "number of rows actually modified/deleted". For this reason, the
  247. SQLAlchemy MySQL dialects always add the ``constants.CLIENT.FOUND_ROWS``
  248. flag, or whatever is equivalent for the target dialect, upon connection.
  249. This setting is currently hardcoded.
  250. .. seealso::
  251. :attr:`.ResultProxy.rowcount`
  252. CAST Support
  253. ------------
  254. MySQL documents the CAST operator as available in version 4.0.2. When using
  255. the SQLAlchemy :func:`.cast` function, SQLAlchemy
  256. will not render the CAST token on MySQL before this version, based on server
  257. version detection, instead rendering the internal expression directly.
  258. CAST may still not be desirable on an early MySQL version post-4.0.2, as it
  259. didn't add all datatype support until 4.1.1. If your application falls into
  260. this narrow area, the behavior of CAST can be controlled using the
  261. :ref:`sqlalchemy.ext.compiler_toplevel` system, as per the recipe below::
  262. from sqlalchemy.sql.expression import Cast
  263. from sqlalchemy.ext.compiler import compiles
  264. @compiles(Cast, 'mysql')
  265. def _check_mysql_version(element, compiler, **kw):
  266. if compiler.dialect.server_version_info < (4, 1, 0):
  267. return compiler.process(element.clause, **kw)
  268. else:
  269. return compiler.visit_cast(element, **kw)
  270. The above function, which only needs to be declared once
  271. within an application, overrides the compilation of the
  272. :func:`.cast` construct to check for version 4.1.0 before
  273. fully rendering CAST; else the internal element of the
  274. construct is rendered directly.
  275. .. _mysql_indexes:
  276. MySQL Specific Index Options
  277. ----------------------------
  278. MySQL-specific extensions to the :class:`.Index` construct are available.
  279. Index Length
  280. ~~~~~~~~~~~~~
  281. MySQL provides an option to create index entries with a certain length, where
  282. "length" refers to the number of characters or bytes in each value which will
  283. become part of the index. SQLAlchemy provides this feature via the
  284. ``mysql_length`` parameter::
  285. Index('my_index', my_table.c.data, mysql_length=10)
  286. Index('a_b_idx', my_table.c.a, my_table.c.b, mysql_length={'a': 4,
  287. 'b': 9})
  288. Prefix lengths are given in characters for nonbinary string types and in bytes
  289. for binary string types. The value passed to the keyword argument *must* be
  290. either an integer (and, thus, specify the same prefix length value for all
  291. columns of the index) or a dict in which keys are column names and values are
  292. prefix length values for corresponding columns. MySQL only allows a length for
  293. a column of an index if it is for a CHAR, VARCHAR, TEXT, BINARY, VARBINARY and
  294. BLOB.
  295. .. versionadded:: 0.8.2 ``mysql_length`` may now be specified as a dictionary
  296. for use with composite indexes.
  297. Index Prefixes
  298. ~~~~~~~~~~~~~~
  299. MySQL storage engines permit you to specify an index prefix when creating
  300. an index. SQLAlchemy provides this feature via the
  301. ``mysql_prefix`` parameter on :class:`.Index`::
  302. Index('my_index', my_table.c.data, mysql_prefix='FULLTEXT')
  303. The value passed to the keyword argument will be simply passed through to the
  304. underlying CREATE INDEX, so it *must* be a valid index prefix for your MySQL
  305. storage engine.
  306. .. versionadded:: 1.1.5
  307. .. seealso::
  308. `CREATE INDEX <http://dev.mysql.com/doc/refman/5.0/en/create-index.html>`_ - \
  309. MySQL documentation
  310. Index Types
  311. ~~~~~~~~~~~~~
  312. Some MySQL storage engines permit you to specify an index type when creating
  313. an index or primary key constraint. SQLAlchemy provides this feature via the
  314. ``mysql_using`` parameter on :class:`.Index`::
  315. Index('my_index', my_table.c.data, mysql_using='hash')
  316. As well as the ``mysql_using`` parameter on :class:`.PrimaryKeyConstraint`::
  317. PrimaryKeyConstraint("data", mysql_using='hash')
  318. The value passed to the keyword argument will be simply passed through to the
  319. underlying CREATE INDEX or PRIMARY KEY clause, so it *must* be a valid index
  320. type for your MySQL storage engine.
  321. More information can be found at:
  322. http://dev.mysql.com/doc/refman/5.0/en/create-index.html
  323. http://dev.mysql.com/doc/refman/5.0/en/create-table.html
  324. .. _mysql_foreign_keys:
  325. MySQL Foreign Keys
  326. ------------------
  327. MySQL's behavior regarding foreign keys has some important caveats.
  328. Foreign Key Arguments to Avoid
  329. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  330. MySQL does not support the foreign key arguments "DEFERRABLE", "INITIALLY",
  331. or "MATCH". Using the ``deferrable`` or ``initially`` keyword argument with
  332. :class:`.ForeignKeyConstraint` or :class:`.ForeignKey` will have the effect of
  333. these keywords being rendered in a DDL expression, which will then raise an
  334. error on MySQL. In order to use these keywords on a foreign key while having
  335. them ignored on a MySQL backend, use a custom compile rule::
  336. from sqlalchemy.ext.compiler import compiles
  337. from sqlalchemy.schema import ForeignKeyConstraint
  338. @compiles(ForeignKeyConstraint, "mysql")
  339. def process(element, compiler, **kw):
  340. element.deferrable = element.initially = None
  341. return compiler.visit_foreign_key_constraint(element, **kw)
  342. .. versionchanged:: 0.9.0 - the MySQL backend no longer silently ignores
  343. the ``deferrable`` or ``initially`` keyword arguments of
  344. :class:`.ForeignKeyConstraint` and :class:`.ForeignKey`.
  345. The "MATCH" keyword is in fact more insidious, and is explicitly disallowed
  346. by SQLAlchemy in conjunction with the MySQL backend. This argument is
  347. silently ignored by MySQL, but in addition has the effect of ON UPDATE and ON
  348. DELETE options also being ignored by the backend. Therefore MATCH should
  349. never be used with the MySQL backend; as is the case with DEFERRABLE and
  350. INITIALLY, custom compilation rules can be used to correct a MySQL
  351. ForeignKeyConstraint at DDL definition time.
  352. .. versionadded:: 0.9.0 - the MySQL backend will raise a
  353. :class:`.CompileError` when the ``match`` keyword is used with
  354. :class:`.ForeignKeyConstraint` or :class:`.ForeignKey`.
  355. Reflection of Foreign Key Constraints
  356. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  357. Not all MySQL storage engines support foreign keys. When using the
  358. very common ``MyISAM`` MySQL storage engine, the information loaded by table
  359. reflection will not include foreign keys. For these tables, you may supply a
  360. :class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
  361. Table('mytable', metadata,
  362. ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
  363. autoload=True
  364. )
  365. .. seealso::
  366. :ref:`mysql_storage_engines`
  367. .. _mysql_unique_constraints:
  368. MySQL Unique Constraints and Reflection
  369. ---------------------------------------
  370. SQLAlchemy supports both the :class:`.Index` construct with the
  371. flag ``unique=True``, indicating a UNIQUE index, as well as the
  372. :class:`.UniqueConstraint` construct, representing a UNIQUE constraint.
  373. Both objects/syntaxes are supported by MySQL when emitting DDL to create
  374. these constraints. However, MySQL does not have a unique constraint
  375. construct that is separate from a unique index; that is, the "UNIQUE"
  376. constraint on MySQL is equivalent to creating a "UNIQUE INDEX".
  377. When reflecting these constructs, the :meth:`.Inspector.get_indexes`
  378. and the :meth:`.Inspector.get_unique_constraints` methods will **both**
  379. return an entry for a UNIQUE index in MySQL. However, when performing
  380. full table reflection using ``Table(..., autoload=True)``,
  381. the :class:`.UniqueConstraint` construct is
  382. **not** part of the fully reflected :class:`.Table` construct under any
  383. circumstances; this construct is always represented by a :class:`.Index`
  384. with the ``unique=True`` setting present in the :attr:`.Table.indexes`
  385. collection.
  386. .. _mysql_timestamp_null:
  387. TIMESTAMP Columns and NULL
  388. --------------------------
  389. MySQL historically enforces that a column which specifies the
  390. TIMESTAMP datatype implicitly includes a default value of
  391. CURRENT_TIMESTAMP, even though this is not stated, and additionally
  392. sets the column as NOT NULL, the opposite behavior vs. that of all
  393. other datatypes::
  394. mysql> CREATE TABLE ts_test (
  395. -> a INTEGER,
  396. -> b INTEGER NOT NULL,
  397. -> c TIMESTAMP,
  398. -> d TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  399. -> e TIMESTAMP NULL);
  400. Query OK, 0 rows affected (0.03 sec)
  401. mysql> SHOW CREATE TABLE ts_test;
  402. +---------+-----------------------------------------------------
  403. | Table | Create Table
  404. +---------+-----------------------------------------------------
  405. | ts_test | CREATE TABLE `ts_test` (
  406. `a` int(11) DEFAULT NULL,
  407. `b` int(11) NOT NULL,
  408. `c` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  409. `d` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  410. `e` timestamp NULL DEFAULT NULL
  411. ) ENGINE=MyISAM DEFAULT CHARSET=latin1
  412. Above, we see that an INTEGER column defaults to NULL, unless it is specified
  413. with NOT NULL. But when the column is of type TIMESTAMP, an implicit
  414. default of CURRENT_TIMESTAMP is generated which also coerces the column
  415. to be a NOT NULL, even though we did not specify it as such.
  416. This behavior of MySQL can be changed on the MySQL side using the
  417. `explicit_defaults_for_timestamp
  418. <http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html
  419. #sysvar_explicit_defaults_for_timestamp>`_ configuration flag introduced in
  420. MySQL 5.6. With this server setting enabled, TIMESTAMP columns behave like
  421. any other datatype on the MySQL side with regards to defaults and nullability.
  422. However, to accommodate the vast majority of MySQL databases that do not
  423. specify this new flag, SQLAlchemy emits the "NULL" specifier explicitly with
  424. any TIMESTAMP column that does not specify ``nullable=False``. In order
  425. to accommodate newer databases that specify ``explicit_defaults_for_timestamp``,
  426. SQLAlchemy also emits NOT NULL for TIMESTAMP columns that do specify
  427. ``nullable=False``. The following example illustrates::
  428. from sqlalchemy import MetaData, Integer, Table, Column, text
  429. from sqlalchemy.dialects.mysql import TIMESTAMP
  430. m = MetaData()
  431. t = Table('ts_test', m,
  432. Column('a', Integer),
  433. Column('b', Integer, nullable=False),
  434. Column('c', TIMESTAMP),
  435. Column('d', TIMESTAMP, nullable=False)
  436. )
  437. from sqlalchemy import create_engine
  438. e = create_engine("mysql://scott:tiger@localhost/test", echo=True)
  439. m.create_all(e)
  440. output::
  441. CREATE TABLE ts_test (
  442. a INTEGER,
  443. b INTEGER NOT NULL,
  444. c TIMESTAMP NULL,
  445. d TIMESTAMP NOT NULL
  446. )
  447. .. versionchanged:: 1.0.0 - SQLAlchemy now renders NULL or NOT NULL in all
  448. cases for TIMESTAMP columns, to accommodate
  449. ``explicit_defaults_for_timestamp``. Prior to this version, it will
  450. not render "NOT NULL" for a TIMESTAMP column that is ``nullable=False``.
  451. """
  452. import re
  453. import sys
  454. import json
  455. from ... import schema as sa_schema
  456. from ... import exc, log, sql, util
  457. from ...sql import compiler, elements
  458. from array import array as _array
  459. from ...engine import reflection
  460. from ...engine import default
  461. from ... import types as sqltypes
  462. from ...util import topological
  463. from ...types import DATE, BOOLEAN, \
  464. BLOB, BINARY, VARBINARY
  465. from . import reflection as _reflection
  466. from .types import BIGINT, BIT, CHAR, DECIMAL, DATETIME, \
  467. DOUBLE, FLOAT, INTEGER, LONGBLOB, LONGTEXT, MEDIUMBLOB, MEDIUMINT, \
  468. MEDIUMTEXT, NCHAR, NUMERIC, NVARCHAR, REAL, SMALLINT, TEXT, TIME, \
  469. TIMESTAMP, TINYBLOB, TINYINT, TINYTEXT, VARCHAR, YEAR
  470. from .types import _StringType, _IntegerType, _NumericType, \
  471. _FloatType, _MatchType
  472. from .enumerated import ENUM, SET
  473. from .json import JSON, JSONIndexType, JSONPathType
  474. RESERVED_WORDS = set(
  475. ['accessible', 'add', 'all', 'alter', 'analyze', 'and', 'as', 'asc',
  476. 'asensitive', 'before', 'between', 'bigint', 'binary', 'blob', 'both',
  477. 'by', 'call', 'cascade', 'case', 'change', 'char', 'character', 'check',
  478. 'collate', 'column', 'condition', 'constraint', 'continue', 'convert',
  479. 'create', 'cross', 'current_date', 'current_time', 'current_timestamp',
  480. 'current_user', 'cursor', 'database', 'databases', 'day_hour',
  481. 'day_microsecond', 'day_minute', 'day_second', 'dec', 'decimal',
  482. 'declare', 'default', 'delayed', 'delete', 'desc', 'describe',
  483. 'deterministic', 'distinct', 'distinctrow', 'div', 'double', 'drop',
  484. 'dual', 'each', 'else', 'elseif', 'enclosed', 'escaped', 'exists',
  485. 'exit', 'explain', 'false', 'fetch', 'float', 'float4', 'float8',
  486. 'for', 'force', 'foreign', 'from', 'fulltext', 'grant', 'group',
  487. 'having', 'high_priority', 'hour_microsecond', 'hour_minute',
  488. 'hour_second', 'if', 'ignore', 'in', 'index', 'infile', 'inner', 'inout',
  489. 'insensitive', 'insert', 'int', 'int1', 'int2', 'int3', 'int4', 'int8',
  490. 'integer', 'interval', 'into', 'is', 'iterate', 'join', 'key', 'keys',
  491. 'kill', 'leading', 'leave', 'left', 'like', 'limit', 'linear', 'lines',
  492. 'load', 'localtime', 'localtimestamp', 'lock', 'long', 'longblob',
  493. 'longtext', 'loop', 'low_priority', 'master_ssl_verify_server_cert',
  494. 'match', 'mediumblob', 'mediumint', 'mediumtext', 'middleint',
  495. 'minute_microsecond', 'minute_second', 'mod', 'modifies', 'natural',
  496. 'not', 'no_write_to_binlog', 'null', 'numeric', 'on', 'optimize',
  497. 'option', 'optionally', 'or', 'order', 'out', 'outer', 'outfile',
  498. 'precision', 'primary', 'procedure', 'purge', 'range', 'read', 'reads',
  499. 'read_only', 'read_write', 'real', 'references', 'regexp', 'release',
  500. 'rename', 'repeat', 'replace', 'require', 'restrict', 'return',
  501. 'revoke', 'right', 'rlike', 'schema', 'schemas', 'second_microsecond',
  502. 'select', 'sensitive', 'separator', 'set', 'show', 'smallint', 'spatial',
  503. 'specific', 'sql', 'sqlexception', 'sqlstate', 'sqlwarning',
  504. 'sql_big_result', 'sql_calc_found_rows', 'sql_small_result', 'ssl',
  505. 'starting', 'straight_join', 'table', 'terminated', 'then', 'tinyblob',
  506. 'tinyint', 'tinytext', 'to', 'trailing', 'trigger', 'true', 'undo',
  507. 'union', 'unique', 'unlock', 'unsigned', 'update', 'usage', 'use',
  508. 'using', 'utc_date', 'utc_time', 'utc_timestamp', 'values', 'varbinary',
  509. 'varchar', 'varcharacter', 'varying', 'when', 'where', 'while', 'with',
  510. 'write', 'x509', 'xor', 'year_month', 'zerofill', # 5.0
  511. 'columns', 'fields', 'privileges', 'soname', 'tables', # 4.1
  512. 'accessible', 'linear', 'master_ssl_verify_server_cert', 'range',
  513. 'read_only', 'read_write', # 5.1
  514. 'general', 'ignore_server_ids', 'master_heartbeat_period', 'maxvalue',
  515. 'resignal', 'signal', 'slow', # 5.5
  516. 'get', 'io_after_gtids', 'io_before_gtids', 'master_bind', 'one_shot',
  517. 'partition', 'sql_after_gtids', 'sql_before_gtids', # 5.6
  518. 'generated', 'optimizer_costs', 'stored', 'virtual', # 5.7
  519. 'admin', 'except', 'grouping', 'of', 'persist', 'recursive',
  520. 'role', # 8.0
  521. ])
  522. AUTOCOMMIT_RE = re.compile(
  523. r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER|LOAD +DATA|REPLACE)',
  524. re.I | re.UNICODE)
  525. SET_RE = re.compile(
  526. r'\s*SET\s+(?:(?:GLOBAL|SESSION)\s+)?\w',
  527. re.I | re.UNICODE)
  528. # old names
  529. MSTime = TIME
  530. MSSet = SET
  531. MSEnum = ENUM
  532. MSLongBlob = LONGBLOB
  533. MSMediumBlob = MEDIUMBLOB
  534. MSTinyBlob = TINYBLOB
  535. MSBlob = BLOB
  536. MSBinary = BINARY
  537. MSVarBinary = VARBINARY
  538. MSNChar = NCHAR
  539. MSNVarChar = NVARCHAR
  540. MSChar = CHAR
  541. MSString = VARCHAR
  542. MSLongText = LONGTEXT
  543. MSMediumText = MEDIUMTEXT
  544. MSTinyText = TINYTEXT
  545. MSText = TEXT
  546. MSYear = YEAR
  547. MSTimeStamp = TIMESTAMP
  548. MSBit = BIT
  549. MSSmallInteger = SMALLINT
  550. MSTinyInteger = TINYINT
  551. MSMediumInteger = MEDIUMINT
  552. MSBigInteger = BIGINT
  553. MSNumeric = NUMERIC
  554. MSDecimal = DECIMAL
  555. MSDouble = DOUBLE
  556. MSReal = REAL
  557. MSFloat = FLOAT
  558. MSInteger = INTEGER
  559. colspecs = {
  560. _IntegerType: _IntegerType,
  561. _NumericType: _NumericType,
  562. _FloatType: _FloatType,
  563. sqltypes.Numeric: NUMERIC,
  564. sqltypes.Float: FLOAT,
  565. sqltypes.Time: TIME,
  566. sqltypes.Enum: ENUM,
  567. sqltypes.MatchType: _MatchType,
  568. sqltypes.JSON: JSON,
  569. sqltypes.JSON.JSONIndexType: JSONIndexType,
  570. sqltypes.JSON.JSONPathType: JSONPathType
  571. }
  572. # Everything 3.23 through 5.1 excepting OpenGIS types.
  573. ischema_names = {
  574. 'bigint': BIGINT,
  575. 'binary': BINARY,
  576. 'bit': BIT,
  577. 'blob': BLOB,
  578. 'boolean': BOOLEAN,
  579. 'char': CHAR,
  580. 'date': DATE,
  581. 'datetime': DATETIME,
  582. 'decimal': DECIMAL,
  583. 'double': DOUBLE,
  584. 'enum': ENUM,
  585. 'fixed': DECIMAL,
  586. 'float': FLOAT,
  587. 'int': INTEGER,
  588. 'integer': INTEGER,
  589. 'json': JSON,
  590. 'longblob': LONGBLOB,
  591. 'longtext': LONGTEXT,
  592. 'mediumblob': MEDIUMBLOB,
  593. 'mediumint': MEDIUMINT,
  594. 'mediumtext': MEDIUMTEXT,
  595. 'nchar': NCHAR,
  596. 'nvarchar': NVARCHAR,
  597. 'numeric': NUMERIC,
  598. 'set': SET,
  599. 'smallint': SMALLINT,
  600. 'text': TEXT,
  601. 'time': TIME,
  602. 'timestamp': TIMESTAMP,
  603. 'tinyblob': TINYBLOB,
  604. 'tinyint': TINYINT,
  605. 'tinytext': TINYTEXT,
  606. 'varbinary': VARBINARY,
  607. 'varchar': VARCHAR,
  608. 'year': YEAR,
  609. }
  610. class MySQLExecutionContext(default.DefaultExecutionContext):
  611. def should_autocommit_text(self, statement):
  612. return AUTOCOMMIT_RE.match(statement)
  613. def create_server_side_cursor(self):
  614. if self.dialect.supports_server_side_cursors:
  615. return self._dbapi_connection.cursor(self.dialect._sscursor)
  616. else:
  617. raise NotImplementedError()
  618. class MySQLCompiler(compiler.SQLCompiler):
  619. render_table_with_column_in_update_from = True
  620. """Overridden from base SQLCompiler value"""
  621. extract_map = compiler.SQLCompiler.extract_map.copy()
  622. extract_map.update({'milliseconds': 'millisecond'})
  623. def visit_random_func(self, fn, **kw):
  624. return "rand%s" % self.function_argspec(fn)
  625. def visit_sysdate_func(self, fn, **kw):
  626. return "SYSDATE()"
  627. def visit_json_getitem_op_binary(self, binary, operator, **kw):
  628. return "JSON_EXTRACT(%s, %s)" % (
  629. self.process(binary.left, **kw),
  630. self.process(binary.right, **kw))
  631. def visit_json_path_getitem_op_binary(self, binary, operator, **kw):
  632. return "JSON_EXTRACT(%s, %s)" % (
  633. self.process(binary.left, **kw),
  634. self.process(binary.right, **kw))
  635. def visit_concat_op_binary(self, binary, operator, **kw):
  636. return "concat(%s, %s)" % (self.process(binary.left),
  637. self.process(binary.right))
  638. def visit_match_op_binary(self, binary, operator, **kw):
  639. return "MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" % \
  640. (self.process(binary.left), self.process(binary.right))
  641. def get_from_hint_text(self, table, text):
  642. return text
  643. def visit_typeclause(self, typeclause, type_=None):
  644. if type_ is None:
  645. type_ = typeclause.type.dialect_impl(self.dialect)
  646. if isinstance(type_, sqltypes.TypeDecorator):
  647. return self.visit_typeclause(typeclause, type_.impl)
  648. elif isinstance(type_, sqltypes.Integer):
  649. if getattr(type_, 'unsigned', False):
  650. return 'UNSIGNED INTEGER'
  651. else:
  652. return 'SIGNED INTEGER'
  653. elif isinstance(type_, sqltypes.TIMESTAMP):
  654. return 'DATETIME'
  655. elif isinstance(type_, (sqltypes.DECIMAL, sqltypes.DateTime,
  656. sqltypes.Date, sqltypes.Time)):
  657. return self.dialect.type_compiler.process(type_)
  658. elif isinstance(type_, sqltypes.String) \
  659. and not isinstance(type_, (ENUM, SET)):
  660. adapted = CHAR._adapt_string_for_cast(type_)
  661. return self.dialect.type_compiler.process(adapted)
  662. elif isinstance(type_, sqltypes._Binary):
  663. return 'BINARY'
  664. elif isinstance(type_, sqltypes.JSON):
  665. return "JSON"
  666. elif isinstance(type_, sqltypes.NUMERIC):
  667. return self.dialect.type_compiler.process(
  668. type_).replace('NUMERIC', 'DECIMAL')
  669. else:
  670. return None
  671. def visit_cast(self, cast, **kw):
  672. # No cast until 4, no decimals until 5.
  673. if not self.dialect._supports_cast:
  674. util.warn(
  675. "Current MySQL version does not support "
  676. "CAST; the CAST will be skipped.")
  677. return self.process(cast.clause.self_group(), **kw)
  678. type_ = self.process(cast.typeclause)
  679. if type_ is None:
  680. util.warn(
  681. "Datatype %s does not support CAST on MySQL; "
  682. "the CAST will be skipped." %
  683. self.dialect.type_compiler.process(cast.typeclause.type))
  684. return self.process(cast.clause.self_group(), **kw)
  685. return 'CAST(%s AS %s)' % (self.process(cast.clause, **kw), type_)
  686. def render_literal_value(self, value, type_):
  687. value = super(MySQLCompiler, self).render_literal_value(value, type_)
  688. if self.dialect._backslash_escapes:
  689. value = value.replace('\\', '\\\\')
  690. return value
  691. # override native_boolean=False behavior here, as
  692. # MySQL still supports native boolean
  693. def visit_true(self, element, **kw):
  694. return "true"
  695. def visit_false(self, element, **kw):
  696. return "false"
  697. def get_select_precolumns(self, select, **kw):
  698. """Add special MySQL keywords in place of DISTINCT.
  699. .. note::
  700. this usage is deprecated. :meth:`.Select.prefix_with`
  701. should be used for special keywords at the start
  702. of a SELECT.
  703. """
  704. if isinstance(select._distinct, util.string_types):
  705. return select._distinct.upper() + " "
  706. elif select._distinct:
  707. return "DISTINCT "
  708. else:
  709. return ""
  710. def visit_join(self, join, asfrom=False, **kwargs):
  711. if join.full:
  712. join_type = " FULL OUTER JOIN "
  713. elif join.isouter:
  714. join_type = " LEFT OUTER JOIN "
  715. else:
  716. join_type = " INNER JOIN "
  717. return ''.join(
  718. (self.process(join.left, asfrom=True, **kwargs),
  719. join_type,
  720. self.process(join.right, asfrom=True, **kwargs),
  721. " ON ",
  722. self.process(join.onclause, **kwargs)))
  723. def for_update_clause(self, select, **kw):
  724. if select._for_update_arg.read:
  725. return " LOCK IN SHARE MODE"
  726. else:
  727. return " FOR UPDATE"
  728. def limit_clause(self, select, **kw):
  729. # MySQL supports:
  730. # LIMIT <limit>
  731. # LIMIT <offset>, <limit>
  732. # and in server versions > 3.3:
  733. # LIMIT <limit> OFFSET <offset>
  734. # The latter is more readable for offsets but we're stuck with the
  735. # former until we can refine dialects by server revision.
  736. limit_clause, offset_clause = select._limit_clause, \
  737. select._offset_clause
  738. if limit_clause is None and offset_clause is None:
  739. return ''
  740. elif offset_clause is not None:
  741. # As suggested by the MySQL docs, need to apply an
  742. # artificial limit if one wasn't provided
  743. # http://dev.mysql.com/doc/refman/5.0/en/select.html
  744. if limit_clause is None:
  745. # hardwire the upper limit. Currently
  746. # needed by OurSQL with Python 3
  747. # (https://bugs.launchpad.net/oursql/+bug/686232),
  748. # but also is consistent with the usage of the upper
  749. # bound as part of MySQL's "syntax" for OFFSET with
  750. # no LIMIT
  751. return ' \n LIMIT %s, %s' % (
  752. self.process(offset_clause, **kw),
  753. "18446744073709551615")
  754. else:
  755. return ' \n LIMIT %s, %s' % (
  756. self.process(offset_clause, **kw),
  757. self.process(limit_clause, **kw))
  758. else:
  759. # No offset provided, so just use the limit
  760. return ' \n LIMIT %s' % (self.process(limit_clause, **kw),)
  761. def update_limit_clause(self, update_stmt):
  762. limit = update_stmt.kwargs.get('%s_limit' % self.dialect.name, None)
  763. if limit:
  764. return "LIMIT %s" % limit
  765. else:
  766. return None
  767. def update_tables_clause(self, update_stmt, from_table,
  768. extra_froms, **kw):
  769. return ', '.join(t._compiler_dispatch(self, asfrom=True, **kw)
  770. for t in [from_table] + list(extra_froms))
  771. def update_from_clause(self, update_stmt, from_table,
  772. extra_froms, from_hints, **kw):
  773. return None
  774. class MySQLDDLCompiler(compiler.DDLCompiler):
  775. def get_column_specification(self, column, **kw):
  776. """Builds column DDL."""
  777. colspec = [
  778. self.preparer.format_column(column),
  779. self.dialect.type_compiler.process(
  780. column.type, type_expression=column)
  781. ]
  782. is_timestamp = isinstance(column.type, sqltypes.TIMESTAMP)
  783. if not column.nullable:
  784. colspec.append('NOT NULL')
  785. # see: http://docs.sqlalchemy.org/en/latest/dialects/
  786. # mysql.html#mysql_timestamp_null
  787. elif column.nullable and is_timestamp:
  788. colspec.append('NULL')
  789. default = self.get_column_default_string(column)
  790. if default is not None:
  791. colspec.append('DEFAULT ' + default)
  792. if column.table is not None \
  793. and column is column.table._autoincrement_column and \
  794. column.server_default is None:
  795. colspec.append('AUTO_INCREMENT')
  796. return ' '.join(colspec)
  797. def post_create_table(self, table):
  798. """Build table-level CREATE options like ENGINE and COLLATE."""
  799. table_opts = []
  800. opts = dict(
  801. (
  802. k[len(self.dialect.name) + 1:].upper(),
  803. v
  804. )
  805. for k, v in table.kwargs.items()
  806. if k.startswith('%s_' % self.dialect.name)
  807. )
  808. partition_options = [
  809. 'PARTITION_BY', 'PARTITIONS', 'SUBPARTITIONS',
  810. 'SUBPARTITION_BY'
  811. ]
  812. nonpart_options = set(opts).difference(partition_options)
  813. part_options = set(opts).intersection(partition_options)
  814. for opt in topological.sort([
  815. ('DEFAULT_CHARSET', 'COLLATE'),
  816. ('DEFAULT_CHARACTER_SET', 'COLLATE'),
  817. ], nonpart_options):
  818. arg = opts[opt]
  819. if opt in _reflection._options_of_type_string:
  820. arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
  821. if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
  822. 'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
  823. 'DEFAULT_CHARSET',
  824. 'DEFAULT_COLLATE'):
  825. opt = opt.replace('_', ' ')
  826. joiner = '='
  827. if opt in ('TABLESPACE', 'DEFAULT CHARACTER SET',
  828. 'CHARACTER SET', 'COLLATE'):
  829. joiner = ' '
  830. table_opts.append(joiner.join((opt, arg)))
  831. for opt in topological.sort([
  832. ('PARTITION_BY', 'PARTITIONS'),
  833. ('PARTITION_BY', 'SUBPARTITION_BY'),
  834. ('PARTITION_BY', 'SUBPARTITIONS'),
  835. ('PARTITIONS', 'SUBPARTITIONS'),
  836. ('PARTITIONS', 'SUBPARTITION_BY'),
  837. ('SUBPARTITION_BY', 'SUBPARTITIONS')
  838. ], part_options):
  839. arg = opts[opt]
  840. if opt in _reflection._options_of_type_string:
  841. arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
  842. opt = opt.replace('_', ' ')
  843. joiner = ' '
  844. table_opts.append(joiner.join((opt, arg)))
  845. return ' '.join(table_opts)
  846. def visit_create_index(self, create):
  847. index = create.element
  848. self._verify_index_table(index)
  849. preparer = self.preparer
  850. table = preparer.format_table(index.table)
  851. columns = [self.sql_compiler.process(expr, include_table=False,
  852. literal_binds=True)
  853. for expr in index.expressions]
  854. name = self._prepared_index_name(index)
  855. text = "CREATE "
  856. if index.unique:
  857. text += "UNIQUE "
  858. index_prefix = index.kwargs.get('mysql_prefix', None)
  859. if index_prefix:
  860. text += index_prefix + ' '
  861. text += "INDEX %s ON %s " % (name, table)
  862. length = index.dialect_options['mysql']['length']
  863. if length is not None:
  864. if isinstance(length, dict):
  865. # length value can be a (column_name --> integer value)
  866. # mapping specifying the prefix length for each column of the
  867. # index
  868. columns = ', '.join(
  869. '%s(%d)' % (expr, length[col.name]) if col.name in length
  870. else
  871. (
  872. '%s(%d)' % (expr, length[expr]) if expr in length
  873. else '%s' % expr
  874. )
  875. for col, expr in zip(index.expressions, columns)
  876. )
  877. else:
  878. # or can be an integer value specifying the same
  879. # prefix length for all columns of the index
  880. columns = ', '.join(
  881. '%s(%d)' % (col, length)
  882. for col in columns
  883. )
  884. else:
  885. columns = ', '.join(columns)
  886. text += '(%s)' % columns
  887. using = index.dialect_options['mysql']['using']
  888. if using is not None:
  889. text += " USING %s" % (preparer.quote(using))
  890. return text
  891. def visit_primary_key_constraint(self, constraint):
  892. text = super(MySQLDDLCompiler, self).\
  893. visit_primary_key_constraint(constraint)
  894. using = constraint.dialect_options['mysql']['using']
  895. if using:
  896. text += " USING %s" % (self.preparer.quote(using))
  897. return text
  898. def visit_drop_index(self, drop):
  899. index = drop.element
  900. return "\nDROP INDEX %s ON %s" % (
  901. self._prepared_index_name(index,
  902. include_schema=False),
  903. self.preparer.format_table(index.table))
  904. def visit_drop_constraint(self, drop):
  905. constraint = drop.element
  906. if isinstance(constraint, sa_schema.ForeignKeyConstraint):
  907. qual = "FOREIGN KEY "
  908. const = self.preparer.format_constraint(constraint)
  909. elif isinstance(constraint, sa_schema.PrimaryKeyConstraint):
  910. qual = "PRIMARY KEY "
  911. const = ""
  912. elif isinstance(constraint, sa_schema.UniqueConstraint):
  913. qual = "INDEX "
  914. const = self.preparer.format_constraint(constraint)
  915. else:
  916. qual = ""
  917. const = self.preparer.format_constraint(constraint)
  918. return "ALTER TABLE %s DROP %s%s" % \
  919. (self.preparer.format_table(constraint.table),
  920. qual, const)
  921. def define_constraint_match(self, constraint):
  922. if constraint.match is not None:
  923. raise exc.CompileError(
  924. "MySQL ignores the 'MATCH' keyword while at the same time "
  925. "causes ON UPDATE/ON DELETE clauses to be ignored.")
  926. return ""
  927. class MySQLTypeCompiler(compiler.GenericTypeCompiler):
  928. def _extend_numeric(self, type_, spec):
  929. "Extend a numeric-type declaration with MySQL specific extensions."
  930. if not self._mysql_type(type_):
  931. return spec
  932. if type_.unsigned:
  933. spec += ' UNSIGNED'
  934. if type_.zerofill:
  935. spec += ' ZEROFILL'
  936. return spec
  937. def _extend_string(self, type_, defaults, spec):
  938. """Extend a string-type declaration with standard SQL CHARACTER SET /
  939. COLLATE annotations and MySQL specific extensions.
  940. """
  941. def attr(name):
  942. return getattr(type_, name, defaults.get(name))
  943. if attr('charset'):
  944. charset = 'CHARACTER SET %s' % attr('charset')
  945. elif attr('ascii'):
  946. charset = 'ASCII'
  947. elif attr('unicode'):
  948. charset = 'UNICODE'
  949. else:
  950. charset = None
  951. if attr('collation'):
  952. collation = 'COLLATE %s' % type_.collation
  953. elif attr('binary'):
  954. collation = 'BINARY'
  955. else:
  956. collation = None
  957. if attr('national'):
  958. # NATIONAL (aka NCHAR/NVARCHAR) trumps charsets.
  959. return ' '.join([c for c in ('NATIONAL', spec, collation)
  960. if c is not None])
  961. return ' '.join([c for c in (spec, charset, collation)
  962. if c is not None])
  963. def _mysql_type(self, type_):
  964. return isinstance(type_, (_StringType, _NumericType))
  965. def visit_NUMERIC(self, type_, **kw):
  966. if type_.precision is None:
  967. return self._extend_numeric(type_, "NUMERIC")
  968. elif type_.scale is None:
  969. return self._extend_numeric(type_,
  970. "NUMERIC(%(precision)s)" %
  971. {'precision': type_.precision})
  972. else:
  973. return self._extend_numeric(type_,
  974. "NUMERIC(%(precision)s, %(scale)s)" %
  975. {'precision': type_.precision,
  976. 'scale': type_.scale})
  977. def visit_DECIMAL(self, type_, **kw):
  978. if type_.precision is None:
  979. return self._extend_numeric(type_, "DECIMAL")
  980. elif type_.scale is None:
  981. return self._extend_numeric(type_,
  982. "DECIMAL(%(precision)s)" %
  983. {'precision': type_.precision})
  984. else:
  985. return self._extend_numeric(type_,
  986. "DECIMAL(%(precision)s, %(scale)s)" %
  987. {'precision': type_.precision,
  988. 'scale': type_.scale})
  989. def visit_DOUBLE(self, type_, **kw):
  990. if type_.precision is not None and type_.scale is not None:
  991. return self._extend_numeric(type_,
  992. "DOUBLE(%(precision)s, %(scale)s)" %
  993. {'precision': type_.precision,
  994. 'scale': type_.scale})
  995. else:
  996. return self._extend_numeric(type_, 'DOUBLE')
  997. def visit_REAL(self, type_, **kw):
  998. if type_.precision is not None and type_.scale is not None:
  999. return self._extend_numeric(type_,
  1000. "REAL(%(precision)s, %(scale)s)" %
  1001. {'precision': type_.precision,
  1002. 'scale': type_.scale})
  1003. else:
  1004. return self._extend_numeric(type_, 'REAL')
  1005. def visit_FLOAT(self, type_, **kw):
  1006. if self._mysql_type(type_) and \
  1007. type_.scale is not None and \
  1008. type_.precision is not None:
  1009. return self._extend_numeric(
  1010. type_, "FLOAT(%s, %s)" % (type_.precision, type_.scale))
  1011. elif type_.precision is not None:
  1012. return self._extend_numeric(type_,
  1013. "FLOAT(%s)" % (type_.precision,))
  1014. else:
  1015. return self._extend_numeric(type_, "FLOAT")
  1016. def visit_INTEGER(self, type_, **kw):
  1017. if self._mysql_type(type_) and type_.display_width is not None:
  1018. return self._extend_numeric(
  1019. type_, "INTEGER(%(display_width)s)" %
  1020. {'display_width': type_.display_width})
  1021. else:
  1022. return self._extend_numeric(type_, "INTEGER")
  1023. def visit_BIGINT(self, type_, **kw):
  1024. if self._mysql_type(type_) and type_.display_width is not None:
  1025. return self._extend_numeric(
  1026. type_, "BIGINT(%(display_width)s)" %
  1027. {'display_width': type_.display_width})
  1028. else:
  1029. return self._extend_numeric(type_, "BIGINT")
  1030. def visit_MEDIUMINT(self, type_, **kw):
  1031. if self._mysql_type(type_) and type_.display_width is not None:
  1032. return self._extend_numeric(
  1033. type_, "MEDIUMINT(%(display_width)s)" %
  1034. {'display_width': type_.display_width})
  1035. else:
  1036. return self._extend_numeric(type_, "MEDIUMINT")
  1037. def visit_TINYINT(self, type_, **kw):
  1038. if self._mysql_type(type_) and type_.display_width is not None:
  1039. return self._extend_numeric(type_,
  1040. "TINYINT(%s)" % type_.display_width)
  1041. else:
  1042. return self._extend_numeric(type_, "TINYINT")
  1043. def visit_SMALLINT(self, type_, **kw):
  1044. if self._mysql_type(type_) and type_.display_width is not None:
  1045. return self._extend_numeric(type_,
  1046. "SMALLINT(%(display_width)s)" %
  1047. {'display_width': type_.display_width}
  1048. )
  1049. else:
  1050. return self._extend_numeric(type_, "SMALLINT")
  1051. def visit_BIT(self, type_, **kw):
  1052. if type_.length is not None:
  1053. return "BIT(%s)" % type_.length
  1054. else:
  1055. return "BIT"
  1056. def visit_DATETIME(self, type_, **kw):
  1057. if getattr(type_, 'fsp', None):
  1058. return "DATETIME(%d)" % type_.fsp
  1059. else:
  1060. return "DATETIME"
  1061. def visit_DATE(self, type_, **kw):
  1062. return "DATE"
  1063. def visit_TIME(self, type_, **kw):
  1064. if getattr(type_, 'fsp', None):
  1065. return "TIME(%d)" % type_.fsp
  1066. else:
  1067. return "TIME"
  1068. def visit_TIMESTAMP(self, type_, **kw):
  1069. if getattr(type_, 'fsp', None):
  1070. return "TIMESTAMP(%d)" % type_.fsp
  1071. else:
  1072. return "TIMESTAMP"
  1073. def visit_YEAR(self, type_, **kw):
  1074. if type_.display_width is None:
  1075. return "YEAR"
  1076. else:
  1077. return "YEAR(%s)" % type_.display_width
  1078. def visit_TEXT(self, type_, **kw):
  1079. if type_.length:
  1080. return self._extend_string(type_, {}, "TEXT(%d)" % type_.length)
  1081. else:
  1082. return self._extend_string(type_, {}, "TEXT")
  1083. def visit_TINYTEXT(self, type_, **kw):
  1084. return self._extend_string(type_, {}, "TINYTEXT")
  1085. def visit_MEDIUMTEXT(self, type_, **kw):
  1086. return self._extend_string(type_, {}, "MEDIUMTEXT")
  1087. def visit_LONGTEXT(self, type_, **kw):
  1088. return self._extend_string(type_, {}, "LONGTEXT")
  1089. def visit_VARCHAR(self, type_, **kw):
  1090. if type_.length:
  1091. return self._extend_string(
  1092. type_, {}, "VARCHAR(%d)" % type_.length)
  1093. else:
  1094. raise exc.CompileError(
  1095. "VARCHAR requires a length on dialect %s" %
  1096. self.dialect.name)
  1097. def visit_CHAR(self, type_, **kw):
  1098. if type_.length:
  1099. return self._extend_string(type_, {}, "CHAR(%(length)s)" %
  1100. {'length': type_.length})
  1101. else:
  1102. return self._extend_string(type_, {}, "CHAR")
  1103. def visit_NVARCHAR(self, type_, **kw):
  1104. # We'll actually generate the equiv. "NATIONAL VARCHAR" instead
  1105. # of "NVARCHAR".
  1106. if type_.length:
  1107. return self._extend_string(
  1108. type_, {'national': True},
  1109. "VARCHAR(%(length)s)" % {'length': type_.length})
  1110. else:
  1111. raise exc.CompileError(
  1112. "NVARCHAR requires a length on dialect %s" %
  1113. self.dialect.name)
  1114. def visit_NCHAR(self, type_, **kw):
  1115. # We'll actually generate the equiv.
  1116. # "NATIONAL CHAR" instead of "NCHAR".
  1117. if type_.length:
  1118. return self._extend_string(
  1119. type_, {'national': True},
  1120. "CHAR(%(length)s)" % {'length': type_.length})
  1121. else:
  1122. return self._extend_string(type_, {'national': True}, "CHAR")
  1123. def visit_VARBINARY(self, type_, **kw):
  1124. return "VARBINARY(%d)" % type_.length
  1125. def visit_JSON(self, type_, **kw):
  1126. return "JSON"
  1127. def visit_large_binary(self, type_, **kw):
  1128. return self.visit_BLOB(type_)
  1129. def visit_enum(self, type_, **kw):
  1130. if not type_.native_enum:
  1131. return super(MySQLTypeCompiler, self).visit_enum(type_)
  1132. else:
  1133. return self._visit_enumerated_values("ENUM", type_, type_.enums)
  1134. def visit_BLOB(self, type_, **kw):
  1135. if type_.length:
  1136. return "BLOB(%d)" % type_.length
  1137. else:
  1138. return "BLOB"
  1139. def visit_TINYBLOB(self, type_, **kw):
  1140. return "TINYBLOB"
  1141. def visit_MEDIUMBLOB(self, type_, **kw):
  1142. return "MEDIUMBLOB"
  1143. def visit_LONGBLOB(self, type_, **kw):
  1144. return "LONGBLOB"
  1145. def _visit_enumerated_values(self, name, type_, enumerated_values):
  1146. quoted_enums = []
  1147. for e in enumerated_values:
  1148. quoted_enums.append("'%s'" % e.replace("'", "''"))
  1149. return self._extend_string(type_, {}, "%s(%s)" % (
  1150. name, ",".join(quoted_enums))
  1151. )
  1152. def visit_ENUM(self, type_, **kw):
  1153. return self._visit_enumerated_values("ENUM", type_,
  1154. type_._enumerated_values)
  1155. def visit_SET(self, type_, **kw):
  1156. return self._visit_enumerated_values("SET", type_,
  1157. type_._enumerated_values)
  1158. def visit_BOOLEAN(self, type, **kw):
  1159. return "BOOL"
  1160. class MySQLIdentifierPreparer(compiler.IdentifierPreparer):
  1161. reserved_words = RESERVED_WORDS
  1162. def __init__(self, dialect, server_ansiquotes=False, **kw):
  1163. if not server_ansiquotes:
  1164. quote = "`"
  1165. else:
  1166. quote = '"'
  1167. super(MySQLIdentifierPreparer, self).__init__(
  1168. dialect,
  1169. initial_quote=quote,
  1170. escape_quote=quote)
  1171. def _quote_free_identifiers(self, *ids):
  1172. """Unilaterally identifier-quote any number of strings."""
  1173. return tuple([self.quote_identifier(i) for i in ids if i is not None])
  1174. @log.class_logger
  1175. class MySQLDialect(default.DefaultDialect):
  1176. """Details of the MySQL dialect.
  1177. Not used directly in application code.
  1178. """
  1179. name = 'mysql'
  1180. supports_alter = True
  1181. # MySQL has no true "boolean" type; we
  1182. # allow for the "true" and "false" keywords, however
  1183. supports_native_boolean = False
  1184. # identifiers are 64, however aliases can be 255...
  1185. max_identifier_length = 255
  1186. max_index_name_length = 64
  1187. supports_native_enum = True
  1188. supports_sane_rowcount = True
  1189. supports_sane_multi_rowcount = False
  1190. supports_multivalues_insert = True
  1191. default_paramstyle = 'format'
  1192. colspecs = colspecs
  1193. statement_compiler = MySQLCompiler
  1194. ddl_compiler = MySQLDDLCompiler
  1195. type_compiler = MySQLTypeCompiler
  1196. ischema_names = ischema_names
  1197. preparer = MySQLIdentifierPreparer
  1198. # default SQL compilation settings -
  1199. # these are modified upon initialize(),
  1200. # i.e. first connect
  1201. _backslash_escapes = True
  1202. _server_ansiquotes = False
  1203. construct_arguments = [
  1204. (sa_schema.Table, {
  1205. "*": None
  1206. }),
  1207. (sql.Update, {
  1208. "limit": None
  1209. }),
  1210. (sa_schema.PrimaryKeyConstraint, {
  1211. "using": None
  1212. }),
  1213. (sa_schema.Index, {
  1214. "using": None,
  1215. "length": None,
  1216. "prefix": None,
  1217. })
  1218. ]
  1219. def __init__(self, isolation_level=None, json_serializer=None,
  1220. json_deserializer=None, **kwargs):
  1221. kwargs.pop('use_ansiquotes', None) # legacy
  1222. default.DefaultDialect.__init__(self, **kwargs)
  1223. self.isolation_level = isolation_level
  1224. self._json_serializer = json_serializer
  1225. self._json_deserializer = json_deserializer
  1226. def on_connect(self):
  1227. if self.isolation_level is not None:
  1228. def connect(conn):
  1229. self.set_isolation_level(conn, self.isolation_level)
  1230. return connect
  1231. else:
  1232. return None
  1233. _isolation_lookup = set(['SERIALIZABLE', 'READ UNCOMMITTED',
  1234. 'READ COMMITTED', 'REPEATABLE READ'])
  1235. def set_isolation_level(self, connection, level):
  1236. level = level.replace('_', ' ')
  1237. # adjust for ConnectionFairy being present
  1238. # allows attribute set e.g. "connection.autocommit = True"
  1239. # to work properly
  1240. if hasattr(connection, 'connection'):
  1241. connection = connection.connection
  1242. self._set_isolation_level(connection, level)
  1243. def _set_isolation_level(self, connection, level):
  1244. if level not in self._isolation_lookup:
  1245. raise exc.ArgumentError(
  1246. "Invalid value '%s' for isolation_level. "
  1247. "Valid isolation levels for %s are %s" %
  1248. (level, self.name, ", ".join(self._isolation_lookup))
  1249. )
  1250. cursor = connection.cursor()
  1251. cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL %s" % level)
  1252. cursor.execute("COMMIT")
  1253. cursor.close()
  1254. def get_isolation_level(self, connection):
  1255. cursor = connection.cursor()
  1256. cursor.execute('SELECT @@tx_isolation')
  1257. val = cursor.fetchone()[0]
  1258. cursor.close()
  1259. if util.py3k and isinstance(val, bytes):
  1260. val = val.decode()
  1261. return val.upper().replace("-", " ")
  1262. def do_commit(self, dbapi_connection):
  1263. """Execute a COMMIT."""
  1264. # COMMIT/ROLLBACK were introduced in 3.23.15.
  1265. # Yes, we have at least one user who has to talk to these old
  1266. # versions!
  1267. #
  1268. # Ignore commit/rollback if support isn't present, otherwise even
  1269. # basic operations via autocommit fail.
  1270. try:
  1271. dbapi_connection.commit()
  1272. except Exception:
  1273. if self.server_version_info < (3, 23, 15):
  1274. args = sys.exc_info()[1].args
  1275. if args and args[0] == 1064:
  1276. return
  1277. raise
  1278. def do_rollback(self, dbapi_connection):
  1279. """Execute a ROLLBACK."""
  1280. try:
  1281. dbapi_connection.rollback()
  1282. except Exception:
  1283. if self.server_version_info < (3, 23, 15):
  1284. args = sys.exc_info()[1].args
  1285. if args and args[0] == 1064:
  1286. return
  1287. raise
  1288. def do_begin_twophase(self, connection, xid):
  1289. connection.execute(sql.text("XA BEGIN :xid"), xid=xid)
  1290. def do_prepare_twophase(self, connection, xid):
  1291. connection.execute(sql.text("XA END :xid"), xid=xid)
  1292. connection.execute(sql.text("XA PREPARE :xid"), xid=xid)
  1293. def do_rollback_twophase(self, connection, xid, is_prepared=True,
  1294. recover=False):
  1295. if not is_prepared:
  1296. connection.execute(sql.text("XA END :xid"), xid=xid)
  1297. connection.execute(sql.text("XA ROLLBACK :xid"), xid=xid)
  1298. def do_commit_twophase(self, connection, xid, is_prepared=True,
  1299. recover=False):
  1300. if not is_prepared:
  1301. self.do_prepare_twophase(connection, xid)
  1302. connection.execute(sql.text("XA COMMIT :xid"), xid=xid)
  1303. def do_recover_twophase(self, connection):
  1304. resultset = connection.execute("XA RECOVER")
  1305. return [row['data'][0:row['gtrid_length']] for row in resultset]
  1306. def is_disconnect(self, e, connection, cursor):
  1307. if isinstance(e, (self.dbapi.OperationalError,
  1308. self.dbapi.ProgrammingError)):
  1309. return self._extract_error_code(e) in \
  1310. (2006, 2013, 2014, 2045, 2055)
  1311. elif isinstance(e, self.dbapi.InterfaceError):
  1312. # if underlying connection is closed,
  1313. # this is the error you get
  1314. return "(0, '')" in str(e)
  1315. else:
  1316. return False
  1317. def _compat_fetchall(self, rp, charset=None):
  1318. """Proxy result rows to smooth over MySQL-Python driver
  1319. inconsistencies."""
  1320. return [_DecodingRowProxy(row, charset) for row in rp.fetchall()]
  1321. def _compat_fetchone(self, rp, charset=None):
  1322. """Proxy a result row to smooth over MySQL-Python driver
  1323. inconsistencies."""
  1324. return _DecodingRowProxy(rp.fetchone(), charset)
  1325. def _compat_first(self, rp, charset=None):
  1326. """Proxy a result row to smooth over MySQL-Python driver
  1327. inconsistencies."""
  1328. return _DecodingRowProxy(rp.first(), charset)
  1329. def _extract_error_code(self, exception):
  1330. raise NotImplementedError()
  1331. def _get_default_schema_name(self, connection):
  1332. return connection.execute('SELECT DATABASE()').scalar()
  1333. def has_table(self, connection, table_name, schema=None):
  1334. # SHOW TABLE STATUS LIKE and SHOW TABLES LIKE do not function properly
  1335. # on macosx (and maybe win?) with multibyte table names.
  1336. #
  1337. # TODO: if this is not a problem on win, make the strategy swappable
  1338. # based on platform. DESCRIBE is slower.
  1339. # [ticket:726]
  1340. # full_name = self.identifier_preparer.format_table(table,
  1341. # use_schema=True)
  1342. full_name = '.'.join(self.identifier_preparer._quote_free_identifiers(
  1343. schema, table_name))
  1344. st = "DESCRIBE %s" % full_name
  1345. rs = None
  1346. try:
  1347. try:
  1348. rs = connection.execution_options(
  1349. skip_user_error_events=True).execute(st)
  1350. have = rs.fetchone() is not None
  1351. rs.close()
  1352. return have
  1353. except exc.DBAPIError as e:
  1354. if self._extract_error_code(e.orig) == 1146:
  1355. return False
  1356. raise
  1357. finally:
  1358. if rs:
  1359. rs.close()
  1360. def initialize(self, connection):
  1361. self._connection_charset = self._detect_charset(connection)
  1362. self._detect_ansiquotes(connection)
  1363. if self._server_ansiquotes:
  1364. # if ansiquotes == True, build a new IdentifierPreparer
  1365. # with the new setting
  1366. self.identifier_preparer = self.preparer(
  1367. self, server_ansiquotes=self._server_ansiquotes)
  1368. default.DefaultDialect.initialize(self, connection)
  1369. @property
  1370. def _is_mariadb(self):
  1371. return 'MariaDB' in self.server_version_info
  1372. @property
  1373. def _supports_cast(self):
  1374. return self.server_version_info is None or \
  1375. self.server_version_info >= (4, 0, 2)
  1376. @reflection.cache
  1377. def get_schema_names(self, connection, **kw):
  1378. rp = connection.execute("SHOW schemas")
  1379. return [r[0] for r in rp]
  1380. @reflection.cache
  1381. def get_table_names(self, connection, schema=None, **kw):
  1382. """Return a Unicode SHOW TABLES from a given schema."""
  1383. if schema is not None:
  1384. current_schema = schema
  1385. else:
  1386. current_schema = self.default_schema_name
  1387. charset = self._connection_charset
  1388. if self.server_version_info < (5, 0, 2):
  1389. rp = connection.execute(
  1390. "SHOW TABLES FROM %s" %
  1391. self.identifier_preparer.quote_identifier(current_schema))
  1392. return [row[0] for
  1393. row in self._compat_fetchall(rp, charset=charset)]
  1394. else:
  1395. rp = connection.execute(
  1396. "SHOW FULL TABLES FROM %s" %
  1397. self.identifier_preparer.quote_identifier(current_schema))
  1398. return [row[0]
  1399. for row in self._compat_fetchall(rp, charset=charset)
  1400. if row[1] == 'BASE TABLE']
  1401. @reflection.cache
  1402. def get_view_names(self, connection, schema=None, **kw):
  1403. if self.server_version_info < (5, 0, 2):
  1404. raise NotImplementedError
  1405. if schema is None:
  1406. schema = self.default_schema_name
  1407. if self.server_version_info < (5, 0, 2):
  1408. return self.get_table_names(connection, schema)
  1409. charset = self._connection_charset
  1410. rp = connection.execute(
  1411. "SHOW FULL TABLES FROM %s" %
  1412. self.identifier_preparer.quote_identifier(schema))
  1413. return [row[0]
  1414. for row in self._compat_fetchall(rp, charset=charset)
  1415. if row[1] in ('VIEW', 'SYSTEM VIEW')]
  1416. @reflection.cache
  1417. def get_table_options(self, connection, table_name, schema=None, **kw):
  1418. parsed_state = self._parsed_state_or_create(
  1419. connection, table_name, schema, **kw)
  1420. return parsed_state.table_options
  1421. @reflection.cache
  1422. def get_columns(self, connection, table_name, schema=None, **kw):
  1423. parsed_state = self._parsed_state_or_create(
  1424. connection, table_name, schema, **kw)
  1425. return parsed_state.columns
  1426. @reflection.cache
  1427. def get_pk_constraint(self, connection, table_name, schema=None, **kw):
  1428. parsed_state = self._parsed_state_or_create(
  1429. connection, table_name, schema, **kw)
  1430. for key in parsed_state.keys:
  1431. if key['type'] == 'PRIMARY':
  1432. # There can be only one.
  1433. cols = [s[0] for s in key['columns']]
  1434. return {'constrained_columns': cols, 'name': None}
  1435. return {'constrained_columns': [], 'name': None}
  1436. @reflection.cache
  1437. def get_foreign_keys(self, connection, table_name, schema=None, **kw):
  1438. parsed_state = self._parsed_state_or_create(
  1439. connection, table_name, schema, **kw)
  1440. default_schema = None
  1441. fkeys = []
  1442. for spec in parsed_state.constraints:
  1443. # only FOREIGN KEYs
  1444. ref_name = spec['table'][-1]
  1445. ref_schema = len(spec['table']) > 1 and \
  1446. spec['table'][-2] or schema
  1447. if not ref_schema:
  1448. if default_schema is None:
  1449. default_schema = \
  1450. connection.dialect.default_schema_name
  1451. if schema == default_schema:
  1452. ref_schema = schema
  1453. loc_names = spec['local']
  1454. ref_names = spec['foreign']
  1455. con_kw = {}
  1456. for opt in ('onupdate', 'ondelete'):
  1457. if spec.get(opt, False):
  1458. con_kw[opt] = spec[opt]
  1459. fkey_d = {
  1460. 'name': spec['name'],
  1461. 'constrained_columns': loc_names,
  1462. 'referred_schema': ref_schema,
  1463. 'referred_table': ref_name,
  1464. 'referred_columns': ref_names,
  1465. 'options': con_kw
  1466. }
  1467. fkeys.append(fkey_d)
  1468. return fkeys
  1469. @reflection.cache
  1470. def get_indexes(self, connection, table_name, schema=None, **kw):
  1471. parsed_state = self._parsed_state_or_create(
  1472. connection, table_name, schema, **kw)
  1473. indexes = []
  1474. for spec in parsed_state.keys:
  1475. unique = False
  1476. flavor = spec['type']
  1477. if flavor == 'PRIMARY':
  1478. continue
  1479. if flavor == 'UNIQUE':
  1480. unique = True
  1481. elif flavor in (None, 'FULLTEXT', 'SPATIAL'):
  1482. pass
  1483. else:
  1484. self.logger.info(
  1485. "Converting unknown KEY type %s to a plain KEY", flavor)
  1486. pass
  1487. index_d = {}
  1488. index_d['name'] = spec['name']
  1489. index_d['column_names'] = [s[0] for s in spec['columns']]
  1490. index_d['unique'] = unique
  1491. if flavor:
  1492. index_d['type'] = flavor
  1493. indexes.append(index_d)
  1494. return indexes
  1495. @reflection.cache
  1496. def get_unique_constraints(self, connection, table_name,
  1497. schema=None, **kw):
  1498. parsed_state = self._parsed_state_or_create(
  1499. connection, table_name, schema, **kw)
  1500. return [
  1501. {
  1502. 'name': key['name'],
  1503. 'column_names': [col[0] for col in key['columns']],
  1504. 'duplicates_index': key['name'],
  1505. }
  1506. for key in parsed_state.keys
  1507. if key['type'] == 'UNIQUE'
  1508. ]
  1509. @reflection.cache
  1510. def get_view_definition(self, connection, view_name, schema=None, **kw):
  1511. charset = self._connection_charset
  1512. full_name = '.'.join(self.identifier_preparer._quote_free_identifiers(
  1513. schema, view_name))
  1514. sql = self._show_create_table(connection, None, charset,
  1515. full_name=full_name)
  1516. return sql
  1517. def _parsed_state_or_create(self, connection, table_name,
  1518. schema=None, **kw):
  1519. return self._setup_parser(
  1520. connection,
  1521. table_name,
  1522. schema,
  1523. info_cache=kw.get('info_cache', None)
  1524. )
  1525. @util.memoized_property
  1526. def _tabledef_parser(self):
  1527. """return the MySQLTableDefinitionParser, generate if needed.
  1528. The deferred creation ensures that the dialect has
  1529. retrieved server version information first.
  1530. """
  1531. if (self.server_version_info < (4, 1) and self._server_ansiquotes):
  1532. # ANSI_QUOTES doesn't affect SHOW CREATE TABLE on < 4.1
  1533. preparer = self.preparer(self, server_ansiquotes=False)
  1534. else:
  1535. preparer = self.identifier_preparer
  1536. return _reflection.MySQLTableDefinitionParser(self, preparer)
  1537. @reflection.cache
  1538. def _setup_parser(self, connection, table_name, schema=None, **kw):
  1539. charset = self._connection_charset
  1540. parser = self._tabledef_parser
  1541. full_name = '.'.join(self.identifier_preparer._quote_free_identifiers(
  1542. schema, table_name))
  1543. sql = self._show_create_table(connection, None, charset,
  1544. full_name=full_name)
  1545. if re.match(r'^CREATE (?:ALGORITHM)?.* VIEW', sql):
  1546. # Adapt views to something table-like.
  1547. columns = self._describe_table(connection, None, charset,
  1548. full_name=full_name)
  1549. sql = parser._describe_to_create(table_name, columns)
  1550. return parser.parse(sql, charset)
  1551. def _detect_charset(self, connection):
  1552. raise NotImplementedError()
  1553. def _detect_casing(self, connection):
  1554. """Sniff out identifier case sensitivity.
  1555. Cached per-connection. This value can not change without a server
  1556. restart.
  1557. """
  1558. # http://dev.mysql.com/doc/refman/5.0/en/name-case-sensitivity.html
  1559. charset = self._connection_charset
  1560. row = self._compat_first(connection.execute(
  1561. "SHOW VARIABLES LIKE 'lower_case_table_names'"),
  1562. charset=charset)
  1563. if not row:
  1564. cs = 0
  1565. else:
  1566. # 4.0.15 returns OFF or ON according to [ticket:489]
  1567. # 3.23 doesn't, 4.0.27 doesn't..
  1568. if row[1] == 'OFF':
  1569. cs = 0
  1570. elif row[1] == 'ON':
  1571. cs = 1
  1572. else:
  1573. cs = int(row[1])
  1574. return cs
  1575. def _detect_collations(self, connection):
  1576. """Pull the active COLLATIONS list from the server.
  1577. Cached per-connection.
  1578. """
  1579. collations = {}
  1580. if self.server_version_info < (4, 1, 0):
  1581. pass
  1582. else:
  1583. charset = self._connection_charset
  1584. rs = connection.execute('SHOW COLLATION')
  1585. for row in self._compat_fetchall(rs, charset):
  1586. collations[row[0]] = row[1]
  1587. return collations
  1588. def _detect_ansiquotes(self, connection):
  1589. """Detect and adjust for the ANSI_QUOTES sql mode."""
  1590. row = self._compat_first(
  1591. connection.execute("SHOW VARIABLES LIKE 'sql_mode'"),
  1592. charset=self._connection_charset)
  1593. if not row:
  1594. mode = ''
  1595. else:
  1596. mode = row[1] or ''
  1597. # 4.0
  1598. if mode.isdigit():
  1599. mode_no = int(mode)
  1600. mode = (mode_no | 4 == mode_no) and 'ANSI_QUOTES' or ''
  1601. self._server_ansiquotes = 'ANSI_QUOTES' in mode
  1602. # as of MySQL 5.0.1
  1603. self._backslash_escapes = 'NO_BACKSLASH_ESCAPES' not in mode
  1604. def _show_create_table(self, connection, table, charset=None,
  1605. full_name=None):
  1606. """Run SHOW CREATE TABLE for a ``Table``."""
  1607. if full_name is None:
  1608. full_name = self.identifier_preparer.format_table(table)
  1609. st = "SHOW CREATE TABLE %s" % full_name
  1610. rp = None
  1611. try:
  1612. rp = connection.execution_options(
  1613. skip_user_error_events=True).execute(st)
  1614. except exc.DBAPIError as e:
  1615. if self._extract_error_code(e.orig) == 1146:
  1616. raise exc.NoSuchTableError(full_name)
  1617. else:
  1618. raise
  1619. row = self._compat_first(rp, charset=charset)
  1620. if not row:
  1621. raise exc.NoSuchTableError(full_name)
  1622. return row[1].strip()
  1623. return sql
  1624. def _describe_table(self, connection, table, charset=None,
  1625. full_name=None):
  1626. """Run DESCRIBE for a ``Table`` and return processed rows."""
  1627. if full_name is None:
  1628. full_name = self.identifier_preparer.format_table(table)
  1629. st = "DESCRIBE %s" % full_name
  1630. rp, rows = None, None
  1631. try:
  1632. try:
  1633. rp = connection.execution_options(
  1634. skip_user_error_events=True).execute(st)
  1635. except exc.DBAPIError as e:
  1636. if self._extract_error_code(e.orig) == 1146:
  1637. raise exc.NoSuchTableError(full_name)
  1638. else:
  1639. raise
  1640. rows = self._compat_fetchall(rp, charset=charset)
  1641. finally:
  1642. if rp:
  1643. rp.close()
  1644. return rows
  1645. class _DecodingRowProxy(object):
  1646. """Return unicode-decoded values based on type inspection.
  1647. Smooth over data type issues (esp. with alpha driver versions) and
  1648. normalize strings as Unicode regardless of user-configured driver
  1649. encoding settings.
  1650. """
  1651. # Some MySQL-python versions can return some columns as
  1652. # sets.Set(['value']) (seriously) but thankfully that doesn't
  1653. # seem to come up in DDL queries.
  1654. _encoding_compat = {
  1655. 'koi8r': 'koi8_r',
  1656. 'koi8u': 'koi8_u',
  1657. 'utf16': 'utf-16-be', # MySQL's uft16 is always bigendian
  1658. 'utf8mb4': 'utf8', # real utf8
  1659. 'eucjpms': 'ujis',
  1660. }
  1661. def __init__(self, rowproxy, charset):
  1662. self.rowproxy = rowproxy
  1663. self.charset = self._encoding_compat.get(charset, charset)
  1664. def __getitem__(self, index):
  1665. item = self.rowproxy[index]
  1666. if isinstance(item, _array):
  1667. item = item.tostring()
  1668. if self.charset and isinstance(item, util.binary_type):
  1669. return item.decode(self.charset)
  1670. else:
  1671. return item
  1672. def __getattr__(self, attr):
  1673. item = getattr(self.rowproxy, attr)
  1674. if isinstance(item, _array):
  1675. item = item.tostring()
  1676. if self.charset and isinstance(item, util.binary_type):
  1677. return item.decode(self.charset)
  1678. else:
  1679. return item