root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/databases/mysql.py @ 3

リビジョン 3, 94.8 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# -*- fill-column: 78 -*-
2# mysql.py
3# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
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
8"""Support for the MySQL database.
9
10Overview
11--------
12
13For normal SQLAlchemy usage, importing this module is unnecessary.  It will be
14loaded on-demand when a MySQL connection is needed.  The generic column types
15like :class:`~sqlalchemy.String` and :class:`~sqlalchemy.Integer` will
16automatically be adapted to the optimal matching MySQL column type.
17
18But if you would like to use one of the MySQL-specific or enhanced column
19types when creating tables with your :class:`~sqlalchemy.Table` definitions,
20then you will need to import them from this module::
21
22  from sqlalchemy.databases import mysql
23
24  Table('mytable', metadata,
25        Column('id', Integer, primary_key=True),
26        Column('ittybittyblob', mysql.MSTinyBlob),
27        Column('biggy', mysql.MSBigInteger(unsigned=True)))
28
29All standard MySQL column types are supported.  The OpenGIS types are
30available for use via table reflection but have no special support or mapping
31to Python classes.  If you're using these types and have opinions about how
32OpenGIS can be smartly integrated into SQLAlchemy please join the mailing
33list!
34
35Supported Versions and Features
36-------------------------------
37
38SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0,
39with capabilities increasing with more modern servers.
40
41Versions 4.1 and higher support the basic SQL functionality that SQLAlchemy
42uses in the ORM and SQL expressions.  These versions pass the applicable tests
43in the suite 100%.  No heroic measures are taken to work around major missing
44SQL features- if your server version does not support sub-selects, for
45example, they won't work in SQLAlchemy either.
46
47Currently, the only DB-API driver supported is `MySQL-Python` (also referred to
48as `MySQLdb`).  Either 1.2.1 or 1.2.2 are recommended.  The alpha, beta and
49gamma releases of 1.2.1 and 1.2.2 should be avoided.  Support for Jython and
50IronPython is planned.
51
52=====================================  ===============
53Feature                                Minimum Version
54=====================================  ===============
55sqlalchemy.orm                         4.1.1
56Table Reflection                       3.23.x
57DDL Generation                         4.1.1
58utf8/Full Unicode Connections          4.1.1
59Transactions                           3.23.15
60Two-Phase Transactions                 5.0.3
61Nested Transactions                    5.0.3
62=====================================  ===============
63
64See the official MySQL documentation for detailed information about features
65supported in any given server release.
66
67Character Sets
68--------------
69
70Many MySQL server installations default to a ``latin1`` encoding for client
71connections.  All data sent through the connection will be converted into
72``latin1``, even if you have ``utf8`` or another character set on your tables
73and columns.  With versions 4.1 and higher, you can change the connection
74character set either through server configuration or by including the
75``charset`` parameter in the URL used for ``create_engine``.  The ``charset``
76option is passed through to MySQL-Python and has the side-effect of also
77enabling ``use_unicode`` in the driver by default.  For regular encoded
78strings, also pass ``use_unicode=0`` in the connection arguments::
79
80  # set client encoding to utf8; all strings come back as unicode
81  create_engine('mysql:///mydb?charset=utf8')
82
83  # set client encoding to utf8; all strings come back as utf8 str
84  create_engine('mysql:///mydb?charset=utf8&use_unicode=0')
85
86Storage Engines
87---------------
88
89Most MySQL server installations have a default table type of ``MyISAM``, a
90non-transactional table type.  During a transaction, non-transactional storage
91engines do not participate and continue to store table changes in autocommit
92mode.  For fully atomic transactions, all participating tables must use a
93transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
94
95Storage engines can be elected when creating tables in SQLAlchemy by supplying
96a ``mysql_engine='whatever'`` to the ``Table`` constructor.  Any MySQL table
97creation option can be specified in this syntax::
98
99  Table('mytable', metadata,
100        Column('data', String(32)),
101        mysql_engine='InnoDB',
102        mysql_charset='utf8'
103       )
104
105Keys
106----
107
108Not all MySQL storage engines support foreign keys.  For ``MyISAM`` and
109similar engines, the information loaded by table reflection will not include
110foreign keys.  For these tables, you may supply a
111:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
112
113  Table('mytable', metadata,
114        ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
115        autoload=True
116       )
117
118When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT``` on
119an integer primary key column::
120
121  >>> t = Table('mytable', metadata,
122  ...   Column('mytable_id', Integer, primary_key=True)
123  ... )
124  >>> t.create()
125  CREATE TABLE mytable (
126          id INTEGER NOT NULL AUTO_INCREMENT,
127          PRIMARY KEY (id)
128  )
129
130You can disable this behavior by supplying ``autoincrement=False`` to the
131:class:`~sqlalchemy.Column`.  This flag can also be used to enable
132auto-increment on a secondary column in a multi-column key for some storage
133engines::
134
135  Table('mytable', metadata,
136        Column('gid', Integer, primary_key=True, autoincrement=False),
137        Column('id', Integer, primary_key=True)
138       )
139
140SQL Mode
141--------
142
143MySQL SQL modes are supported.  Modes that enable ``ANSI_QUOTES`` (such as
144``ANSI``) require an engine option to modify SQLAlchemy's quoting style.
145When using an ANSI-quoting mode, supply ``use_ansiquotes=True`` when
146creating your ``Engine``::
147
148  create_engine('mysql://localhost/test', use_ansiquotes=True)
149
150This is an engine-wide option and is not toggleable on a per-connection basis.
151SQLAlchemy does not presume to ``SET sql_mode`` for you with this option.  For
152the best performance, set the quoting style server-wide in ``my.cnf`` or by
153supplying ``--sql-mode`` to ``mysqld``.  You can also use a
154:class:`sqlalchemy.pool.Pool` listener hook to issue a ``SET SESSION
155sql_mode='...'`` on connect to configure each connection.
156
157If you do not specify ``use_ansiquotes``, the regular MySQL quoting style is
158used by default.
159
160If you do issue a ``SET sql_mode`` through SQLAlchemy, the dialect must be
161updated if the quoting style is changed.  Again, this change will affect all
162connections::
163
164  connection.execute('SET sql_mode="ansi"')
165  connection.dialect.use_ansiquotes = True
166
167MySQL SQL Extensions
168--------------------
169
170Many of the MySQL SQL extensions are handled through SQLAlchemy's generic
171function and operator support::
172
173  table.select(table.c.password==func.md5('plaintext'))
174  table.select(table.c.username.op('regexp')('^[a-d]'))
175
176And of course any valid MySQL statement can be executed as a string as well.
177
178Some limited direct support for MySQL extensions to SQL is currently
179available.
180
181  * SELECT pragma::
182
183      select(..., prefixes=['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
184
185  * UPDATE with LIMIT::
186
187      update(..., mysql_limit=10)
188
189Troubleshooting
190---------------
191
192If you have problems that seem server related, first check that you are
193using the most recent stable MySQL-Python package available.  The Database
194Notes page on the wiki at http://www.sqlalchemy.org is a good resource for
195timely information affecting MySQL in SQLAlchemy.
196
197"""
198
199import datetime, decimal, inspect, re, sys
200from array import array as _array
201
202from sqlalchemy import exc, log, schema, sql, util
203from sqlalchemy.sql import operators as sql_operators
204from sqlalchemy.sql import functions as sql_functions
205from sqlalchemy.sql import compiler
206
207from sqlalchemy.engine import base as engine_base, default
208from sqlalchemy import types as sqltypes
209
210
211__all__ = (
212    'MSBigInteger', 'MSMediumInteger', 'MSBinary', 'MSBit', 'MSBlob', 'MSBoolean',
213    'MSChar', 'MSDate', 'MSDateTime', 'MSDecimal', 'MSDouble',
214    'MSEnum', 'MSFloat', 'MSInteger', 'MSLongBlob', 'MSLongText',
215    'MSMediumBlob', 'MSMediumText', 'MSNChar', 'MSNVarChar',
216    'MSNumeric', 'MSSet', 'MSSmallInteger', 'MSString', 'MSText',
217    'MSTime', 'MSTimeStamp', 'MSTinyBlob', 'MSTinyInteger',
218    'MSTinyText', 'MSVarBinary', 'MSYear' )
219
220
221RESERVED_WORDS = set(
222    ['accessible', 'add', 'all', 'alter', 'analyze','and', 'as', 'asc',
223     'asensitive', 'before', 'between', 'bigint', 'binary', 'blob', 'both',
224     'by', 'call', 'cascade', 'case', 'change', 'char', 'character', 'check',
225     'collate', 'column', 'condition', 'constraint', 'continue', 'convert',
226     'create', 'cross', 'current_date', 'current_time', 'current_timestamp',
227     'current_user', 'cursor', 'database', 'databases', 'day_hour',
228     'day_microsecond', 'day_minute', 'day_second', 'dec', 'decimal',
229     'declare', 'default', 'delayed', 'delete', 'desc', 'describe',
230     'deterministic', 'distinct', 'distinctrow', 'div', 'double', 'drop',
231     'dual', 'each', 'else', 'elseif', 'enclosed', 'escaped', 'exists',
232     'exit', 'explain', 'false', 'fetch', 'float', 'float4', 'float8',
233     'for', 'force', 'foreign', 'from', 'fulltext', 'grant', 'group', 'having',
234     'high_priority', 'hour_microsecond', 'hour_minute', 'hour_second', 'if',
235     'ignore', 'in', 'index', 'infile', 'inner', 'inout', 'insensitive',
236     'insert', 'int', 'int1', 'int2', 'int3', 'int4', 'int8', 'integer',
237     'interval', 'into', 'is', 'iterate', 'join', 'key', 'keys', 'kill',
238     'leading', 'leave', 'left', 'like', 'limit', 'linear', 'lines', 'load',
239     'localtime', 'localtimestamp', 'lock', 'long', 'longblob', 'longtext',
240     'loop', 'low_priority', 'master_ssl_verify_server_cert', 'match',
241     'mediumblob', 'mediumint', 'mediumtext', 'middleint',
242     'minute_microsecond', 'minute_second', 'mod', 'modifies', 'natural',
243     'not', 'no_write_to_binlog', 'null', 'numeric', 'on', 'optimize',
244     'option', 'optionally', 'or', 'order', 'out', 'outer', 'outfile',
245     'precision', 'primary', 'procedure', 'purge', 'range', 'read', 'reads',
246     'read_only', 'read_write', 'real', 'references', 'regexp', 'release',
247     'rename', 'repeat', 'replace', 'require', 'restrict', 'return',
248     'revoke', 'right', 'rlike', 'schema', 'schemas', 'second_microsecond',
249     'select', 'sensitive', 'separator', 'set', 'show', 'smallint', 'spatial',
250     'specific', 'sql', 'sqlexception', 'sqlstate', 'sqlwarning',
251     'sql_big_result', 'sql_calc_found_rows', 'sql_small_result', 'ssl',
252     'starting', 'straight_join', 'table', 'terminated', 'then', 'tinyblob',
253     'tinyint', 'tinytext', 'to', 'trailing', 'trigger', 'true', 'undo',
254     'union', 'unique', 'unlock', 'unsigned', 'update', 'usage', 'use',
255     'using', 'utc_date', 'utc_time', 'utc_timestamp', 'values', 'varbinary',
256     'varchar', 'varcharacter', 'varying', 'when', 'where', 'while', 'with',
257     'write', 'x509', 'xor', 'year_month', 'zerofill', # 5.0
258     'columns', 'fields', 'privileges', 'soname', 'tables', # 4.1
259     'accessible', 'linear', 'master_ssl_verify_server_cert', 'range',
260     'read_only', 'read_write', # 5.1
261     ])
262
263AUTOCOMMIT_RE = re.compile(
264    r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER|LOAD +DATA|REPLACE)',
265    re.I | re.UNICODE)
266SET_RE = re.compile(
267    r'\s*SET\s+(?:(?:GLOBAL|SESSION)\s+)?\w',
268    re.I | re.UNICODE)
269
270
271class _NumericType(object):
272    """Base for MySQL numeric types."""
273
274    def __init__(self, kw):
275        self.unsigned = kw.pop('unsigned', False)
276        self.zerofill = kw.pop('zerofill', False)
277
278    def _extend(self, spec):
279        "Extend a numeric-type declaration with MySQL specific extensions."
280
281        if self.unsigned:
282            spec += ' UNSIGNED'
283        if self.zerofill:
284            spec += ' ZEROFILL'
285        return spec
286
287
288class _StringType(object):
289    """Base for MySQL string types."""
290
291    def __init__(self, charset=None, collation=None,
292                 ascii=False, unicode=False, binary=False,
293                 national=False, **kwargs):
294        self.charset = charset
295        # allow collate= or collation=
296        self.collation = kwargs.get('collate', collation)
297        self.ascii = ascii
298        self.unicode = unicode
299        self.binary = binary
300        self.national = national
301
302    def _extend(self, spec):
303        """Extend a string-type declaration with standard SQL CHARACTER SET /
304        COLLATE annotations and MySQL specific extensions.
305        """
306
307        if self.charset:
308            charset = 'CHARACTER SET %s' % self.charset
309        elif self.ascii:
310            charset = 'ASCII'
311        elif self.unicode:
312            charset = 'UNICODE'
313        else:
314            charset = None
315
316        if self.collation:
317            collation = 'COLLATE %s' % self.collation
318        elif self.binary:
319            collation = 'BINARY'
320        else:
321            collation = None
322
323        if self.national:
324            # NATIONAL (aka NCHAR/NVARCHAR) trumps charsets.
325            return ' '.join([c for c in ('NATIONAL', spec, collation)
326                             if c is not None])
327        return ' '.join([c for c in (spec, charset, collation)
328                         if c is not None])
329
330    def __repr__(self):
331        attributes = inspect.getargspec(self.__init__)[0][1:]
332        attributes.extend(inspect.getargspec(_StringType.__init__)[0][1:])
333
334        params = {}
335        for attr in attributes:
336            val = getattr(self, attr)
337            if val is not None and val is not False:
338                params[attr] = val
339
340        return "%s(%s)" % (self.__class__.__name__,
341                           ', '.join(['%s=%r' % (k, params[k]) for k in params]))
342
343
344class MSNumeric(sqltypes.Numeric, _NumericType):
345    """MySQL NUMERIC type."""
346
347    def __init__(self, precision=10, scale=2, asdecimal=True, **kw):
348        """Construct a NUMERIC.
349
350        :param precision: Total digits in this number.  If scale and precision
351          are both None, values are stored to limits allowed by the server.
352
353        :param scale: The number of digits after the decimal point.
354
355        :param unsigned: a boolean, optional.
356
357        :param zerofill: Optional. If true, values will be stored as strings
358          left-padded with zeros. Note that this does not effect the values
359          returned by the underlying database API, which continue to be
360          numeric.
361
362        """
363        _NumericType.__init__(self, kw)
364        sqltypes.Numeric.__init__(self, precision, scale, asdecimal=asdecimal, **kw)
365
366    def get_col_spec(self):
367        if self.precision is None:
368            return self._extend("NUMERIC")
369        else:
370            return self._extend("NUMERIC(%(precision)s, %(scale)s)" % {'precision': self.precision, 'scale' : self.scale})
371
372    def bind_processor(self, dialect):
373        return None
374
375    def result_processor(self, dialect):
376        if not self.asdecimal:
377            def process(value):
378                if isinstance(value, decimal.Decimal):
379                    return float(value)
380                else:
381                    return value
382            return process
383        else:
384            return None
385
386
387class MSDecimal(MSNumeric):
388    """MySQL DECIMAL type."""
389
390    def __init__(self, precision=10, scale=2, asdecimal=True, **kw):
391        """Construct a DECIMAL.
392
393        :param precision: Total digits in this number.  If scale and precision
394          are both None, values are stored to limits allowed by the server.
395
396        :param scale: The number of digits after the decimal point.
397
398        :param unsigned: a boolean, optional.
399
400        :param zerofill: Optional. If true, values will be stored as strings
401          left-padded with zeros. Note that this does not effect the values
402          returned by the underlying database API, which continue to be
403          numeric.
404
405        """
406        super(MSDecimal, self).__init__(precision, scale, asdecimal=asdecimal, **kw)
407
408    def get_col_spec(self):
409        if self.precision is None:
410            return self._extend("DECIMAL")
411        elif self.scale is None:
412            return self._extend("DECIMAL(%(precision)s)" % {'precision': self.precision})
413        else:
414            return self._extend("DECIMAL(%(precision)s, %(scale)s)" % {'precision': self.precision, 'scale' : self.scale})
415
416
417class MSDouble(sqltypes.Float, _NumericType):
418    """MySQL DOUBLE type."""
419
420    def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
421        """Construct a DOUBLE.
422
423        :param precision: Total digits in this number.  If scale and precision
424          are both None, values are stored to limits allowed by the server.
425
426        :param scale: The number of digits after the decimal point.
427
428        :param unsigned: a boolean, optional.
429
430        :param zerofill: Optional. If true, values will be stored as strings
431          left-padded with zeros. Note that this does not effect the values
432          returned by the underlying database API, which continue to be
433          numeric.
434
435        """
436        if ((precision is None and scale is not None) or
437            (precision is not None and scale is None)):
438            raise exc.ArgumentError(
439                "You must specify both precision and scale or omit "
440                "both altogether.")
441
442        _NumericType.__init__(self, kw)
443        sqltypes.Float.__init__(self, asdecimal=asdecimal, **kw)
444        self.scale = scale
445        self.precision = precision
446
447    def get_col_spec(self):
448        if self.precision is not None and self.scale is not None:
449            return self._extend("DOUBLE(%(precision)s, %(scale)s)" %
450                                {'precision': self.precision,
451                                 'scale' : self.scale})
452        else:
453            return self._extend('DOUBLE')
454
455
456class MSReal(MSDouble):
457    """MySQL REAL type."""
458
459    def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
460        """Construct a REAL.
461
462        :param precision: Total digits in this number.  If scale and precision
463          are both None, values are stored to limits allowed by the server.
464
465        :param scale: The number of digits after the decimal point.
466
467        :param unsigned: a boolean, optional.
468
469        :param zerofill: Optional. If true, values will be stored as strings
470          left-padded with zeros. Note that this does not effect the values
471          returned by the underlying database API, which continue to be
472          numeric.
473
474        """
475        MSDouble.__init__(self, precision, scale, asdecimal, **kw)
476
477    def get_col_spec(self):
478        if self.precision is not None and self.scale is not None:
479            return self._extend("REAL(%(precision)s, %(scale)s)" %
480                                {'precision': self.precision,
481                                 'scale' : self.scale})
482        else:
483            return self._extend('REAL')
484
485
486class MSFloat(sqltypes.Float, _NumericType):
487    """MySQL FLOAT type."""
488
489    def __init__(self, precision=None, scale=None, asdecimal=False, **kw):
490        """Construct a FLOAT.
491
492        :param precision: Total digits in this number.  If scale and precision
493          are both None, values are stored to limits allowed by the server.
494
495        :param scale: The number of digits after the decimal point.
496
497        :param unsigned: a boolean, optional.
498
499        :param zerofill: Optional. If true, values will be stored as strings
500          left-padded with zeros. Note that this does not effect the values
501          returned by the underlying database API, which continue to be
502          numeric.
503
504        """
505        _NumericType.__init__(self, kw)
506        sqltypes.Float.__init__(self, asdecimal=asdecimal, **kw)
507        self.scale = scale
508        self.precision = precision
509
510    def get_col_spec(self):
511        if self.scale is not None and self.precision is not None:
512            return self._extend("FLOAT(%s, %s)" % (self.precision, self.scale))
513        elif self.precision is not None:
514            return self._extend("FLOAT(%s)" % (self.precision,))
515        else:
516            return self._extend("FLOAT")
517
518    def bind_processor(self, dialect):
519        return None
520
521
522class MSInteger(sqltypes.Integer, _NumericType):
523    """MySQL INTEGER type."""
524
525    def __init__(self, display_width=None, **kw):
526        """Construct an INTEGER.
527
528        :param display_width: Optional, maximum display width for this number.
529
530        :param unsigned: a boolean, optional.
531
532        :param zerofill: Optional. If true, values will be stored as strings
533          left-padded with zeros. Note that this does not effect the values
534          returned by the underlying database API, which continue to be
535          numeric.
536
537        """
538        if 'length' in kw:
539            util.warn_deprecated("'length' is deprecated for MSInteger and subclasses.  Use 'display_width'.")
540            self.display_width = kw.pop('length')
541        else:
542            self.display_width = display_width
543        _NumericType.__init__(self, kw)
544        sqltypes.Integer.__init__(self, **kw)
545
546    def get_col_spec(self):
547        if self.display_width is not None:
548            return self._extend("INTEGER(%(display_width)s)" % {'display_width': self.display_width})
549        else:
550            return self._extend("INTEGER")
551
552
553class MSBigInteger(MSInteger):
554    """MySQL BIGINTEGER type."""
555
556    def __init__(self, display_width=None, **kw):
557        """Construct a BIGINTEGER.
558
559        :param display_width: Optional, maximum display width for this number.
560
561        :param unsigned: a boolean, optional.
562
563        :param zerofill: Optional. If true, values will be stored as strings
564          left-padded with zeros. Note that this does not effect the values
565          returned by the underlying database API, which continue to be
566          numeric.
567
568        """
569        super(MSBigInteger, self).__init__(display_width, **kw)
570
571    def get_col_spec(self):
572        if self.display_width is not None:
573            return self._extend("BIGINT(%(display_width)s)" % {'display_width': self.display_width})
574        else:
575            return self._extend("BIGINT")
576
577
578class MSMediumInteger(MSInteger):
579    """MySQL MEDIUMINTEGER type."""
580
581    def __init__(self, display_width=None, **kw):
582        """Construct a MEDIUMINTEGER
583
584        :param display_width: Optional, maximum display width for this number.
585
586        :param unsigned: a boolean, optional.
587
588        :param zerofill: Optional. If true, values will be stored as strings
589          left-padded with zeros. Note that this does not effect the values
590          returned by the underlying database API, which continue to be
591          numeric.
592
593        """
594        super(MSMediumInteger, self).__init__(display_width, **kw)
595
596    def get_col_spec(self):
597        if self.display_width is not None:
598            return self._extend("MEDIUMINT(%(display_width)s)" % {'display_width': self.display_width})
599        else:
600            return self._extend("MEDIUMINT")
601
602
603
604class MSTinyInteger(MSInteger):
605    """MySQL TINYINT type."""
606
607    def __init__(self, display_width=None, **kw):
608        """Construct a TINYINT.
609
610        Note: following the usual MySQL conventions, TINYINT(1) columns
611        reflected during Table(..., autoload=True) are treated as
612        Boolean columns.
613
614        :param display_width: Optional, maximum display width for this number.
615
616        :param unsigned: a boolean, optional.
617
618        :param zerofill: Optional. If true, values will be stored as strings
619          left-padded with zeros. Note that this does not effect the values
620          returned by the underlying database API, which continue to be
621          numeric.
622
623        """
624        super(MSTinyInteger, self).__init__(display_width, **kw)
625
626    def get_col_spec(self):
627        if self.display_width is not None:
628            return self._extend("TINYINT(%s)" % self.display_width)
629        else:
630            return self._extend("TINYINT")
631
632
633class MSSmallInteger(sqltypes.Smallinteger, MSInteger):
634    """MySQL SMALLINTEGER type."""
635
636    def __init__(self, display_width=None, **kw):
637        """Construct a SMALLINTEGER.
638
639        :param display_width: Optional, maximum display width for this number.
640
641        :param unsigned: a boolean, optional.
642
643        :param zerofill: Optional. If true, values will be stored as strings
644          left-padded with zeros. Note that this does not effect the values
645          returned by the underlying database API, which continue to be
646          numeric.
647
648        """
649        self.display_width = display_width
650        _NumericType.__init__(self, kw)
651        sqltypes.SmallInteger.__init__(self, **kw)
652
653    def get_col_spec(self):
654        if self.display_width is not None:
655            return self._extend("SMALLINT(%(display_width)s)" % {'display_width': self.display_width})
656        else:
657            return self._extend("SMALLINT")
658
659
660class MSBit(sqltypes.TypeEngine):
661    """MySQL BIT type.
662
663    This type is for MySQL 5.0.3 or greater for MyISAM, and 5.0.5 or greater for
664    MyISAM, MEMORY, InnoDB and BDB.  For older versions, use a MSTinyInteger()
665    type.
666
667    """
668
669    def __init__(self, length=None):
670        """Construct a BIT.
671
672        :param length: Optional, number of bits.
673
674        """
675        self.length = length
676
677    def result_processor(self, dialect):
678        """Convert a MySQL's 64 bit, variable length binary string to a long."""
679        def process(value):
680            if value is not None:
681                v = 0L
682                for i in map(ord, value):
683                    v = v << 8 | i
684                value = v
685            return value
686        return process
687
688    def get_col_spec(self):
689        if self.length is not None:
690            return "BIT(%s)" % self.length
691        else:
692            return "BIT"
693
694
695class MSDateTime(sqltypes.DateTime):
696    """MySQL DATETIME type."""
697
698    def get_col_spec(self):
699        return "DATETIME"
700
701
702class MSDate(sqltypes.Date):
703    """MySQL DATE type."""
704
705    def get_col_spec(self):
706        return "DATE"
707
708
709class MSTime(sqltypes.Time):
710    """MySQL TIME type."""
711
712    def get_col_spec(self):
713        return "TIME"
714
715    def result_processor(self, dialect):
716        def process(value):
717            # convert from a timedelta value
718            if value is not None:
719                return datetime.time(value.seconds/60/60, value.seconds/60%60, value.seconds - (value.seconds/60*60))
720            else:
721                return None
722        return process
723
724class MSTimeStamp(sqltypes.TIMESTAMP):
725    """MySQL TIMESTAMP type.
726
727    To signal the orm to automatically re-select modified rows to retrieve the
728    updated timestamp, add a ``server_default`` to your
729    :class:`~sqlalchemy.Column` specification::
730
731        from sqlalchemy.databases import mysql
732        Column('updated', mysql.MSTimeStamp,
733               server_default=sql.text('CURRENT_TIMESTAMP')
734              )
735
736    The full range of MySQL 4.1+ TIMESTAMP defaults can be specified in
737    the the default::
738
739        server_default=sql.text('CURRENT TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
740
741    """
742
743    def get_col_spec(self):
744        return "TIMESTAMP"
745
746
747class MSYear(sqltypes.TypeEngine):
748    """MySQL YEAR type, for single byte storage of years 1901-2155."""
749
750    def __init__(self, display_width=None):
751        self.display_width = display_width
752
753    def get_col_spec(self):
754        if self.display_width is None:
755            return "YEAR"
756        else:
757            return "YEAR(%s)" % self.display_width
758
759class MSText(_StringType, sqltypes.Text):
760    """MySQL TEXT type, for text up to 2^16 characters."""
761
762    def __init__(self, length=None, **kwargs):
763        """Construct a TEXT.
764
765        :param length: Optional, if provided the server may optimize storage
766          by substituting the smallest TEXT type sufficient to store
767          ``length`` characters.
768
769        :param charset: Optional, a column-level character set for this string
770          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
771
772        :param collation: Optional, a column-level collation for this string
773          value.  Takes precedence to 'binary' short-hand.
774
775        :param ascii: Defaults to False: short-hand for the ``latin1``
776          character set, generates ASCII in schema.
777
778        :param unicode: Defaults to False: short-hand for the ``ucs2``
779          character set, generates UNICODE in schema.
780
781        :param national: Optional. If true, use the server's configured
782          national character set.
783
784        :param binary: Defaults to False: short-hand, pick the binary
785          collation type that matches the column's character set.  Generates
786          BINARY in schema.  This does not affect the type of data stored,
787          only the collation of character data.
788
789        """
790        _StringType.__init__(self, **kwargs)
791        sqltypes.Text.__init__(self, length,
792                               kwargs.get('convert_unicode', False), kwargs.get('assert_unicode', None))
793
794    def get_col_spec(self):
795        if self.length:
796            return self._extend("TEXT(%d)" % self.length)
797        else:
798            return self._extend("TEXT")
799
800
801class MSTinyText(MSText):
802    """MySQL TINYTEXT type, for text up to 2^8 characters."""
803
804    def __init__(self, **kwargs):
805        """Construct a TINYTEXT.
806
807        :param charset: Optional, a column-level character set for this string
808          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
809
810        :param collation: Optional, a column-level collation for this string
811          value.  Takes precedence to 'binary' short-hand.
812
813        :param ascii: Defaults to False: short-hand for the ``latin1``
814          character set, generates ASCII in schema.
815
816        :param unicode: Defaults to False: short-hand for the ``ucs2``
817          character set, generates UNICODE in schema.
818
819        :param national: Optional. If true, use the server's configured
820          national character set.
821
822        :param binary: Defaults to False: short-hand, pick the binary
823          collation type that matches the column's character set.  Generates
824          BINARY in schema.  This does not affect the type of data stored,
825          only the collation of character data.
826
827        """
828
829        super(MSTinyText, self).__init__(**kwargs)
830
831    def get_col_spec(self):
832        return self._extend("TINYTEXT")
833
834
835class MSMediumText(MSText):
836    """MySQL MEDIUMTEXT type, for text up to 2^24 characters."""
837
838    def __init__(self, **kwargs):
839        """Construct a MEDIUMTEXT.
840
841        :param charset: Optional, a column-level character set for this string
842          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
843
844        :param collation: Optional, a column-level collation for this string
845          value.  Takes precedence to 'binary' short-hand.
846
847        :param ascii: Defaults to False: short-hand for the ``latin1``
848          character set, generates ASCII in schema.
849
850        :param unicode: Defaults to False: short-hand for the ``ucs2``
851          character set, generates UNICODE in schema.
852
853        :param national: Optional. If true, use the server's configured
854          national character set.
855
856        :param binary: Defaults to False: short-hand, pick the binary
857          collation type that matches the column's character set.  Generates
858          BINARY in schema.  This does not affect the type of data stored,
859          only the collation of character data.
860
861        """
862        super(MSMediumText, self).__init__(**kwargs)
863
864    def get_col_spec(self):
865        return self._extend("MEDIUMTEXT")
866
867
868class MSLongText(MSText):
869    """MySQL LONGTEXT type, for text up to 2^32 characters."""
870
871    def __init__(self, **kwargs):
872        """Construct a LONGTEXT.
873
874        :param charset: Optional, a column-level character set for this string
875          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
876
877        :param collation: Optional, a column-level collation for this string
878          value.  Takes precedence to 'binary' short-hand.
879
880        :param ascii: Defaults to False: short-hand for the ``latin1``
881          character set, generates ASCII in schema.
882
883        :param unicode: Defaults to False: short-hand for the ``ucs2``
884          character set, generates UNICODE in schema.
885
886        :param national: Optional. If true, use the server's configured
887          national character set.
888
889        :param binary: Defaults to False: short-hand, pick the binary
890          collation type that matches the column's character set.  Generates
891          BINARY in schema.  This does not affect the type of data stored,
892          only the collation of character data.
893
894        """
895        super(MSLongText, self).__init__(**kwargs)
896
897    def get_col_spec(self):
898        return self._extend("LONGTEXT")
899
900
901class MSString(_StringType, sqltypes.String):
902    """MySQL VARCHAR type, for variable-length character data."""
903
904    def __init__(self, length=None, **kwargs):
905        """Construct a VARCHAR.
906
907        :param charset: Optional, a column-level character set for this string
908          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
909
910        :param collation: Optional, a column-level collation for this string
911          value.  Takes precedence to 'binary' short-hand.
912
913        :param ascii: Defaults to False: short-hand for the ``latin1``
914          character set, generates ASCII in schema.
915
916        :param unicode: Defaults to False: short-hand for the ``ucs2``
917          character set, generates UNICODE in schema.
918
919        :param national: Optional. If true, use the server's configured
920          national character set.
921
922        :param binary: Defaults to False: short-hand, pick the binary
923          collation type that matches the column's character set.  Generates
924          BINARY in schema.  This does not affect the type of data stored,
925          only the collation of character data.
926
927        """
928        _StringType.__init__(self, **kwargs)
929        sqltypes.String.__init__(self, length,
930                                 kwargs.get('convert_unicode', False), kwargs.get('assert_unicode', None))
931
932    def get_col_spec(self):
933        if self.length:
934            return self._extend("VARCHAR(%d)" % self.length)
935        else:
936            return self._extend("VARCHAR")
937
938
939class MSChar(_StringType, sqltypes.CHAR):
940    """MySQL CHAR type, for fixed-length character data."""
941
942    def __init__(self, length, **kwargs):
943        """Construct an NCHAR.
944
945        :param length: Maximum data length, in characters.
946
947        :param binary: Optional, use the default binary collation for the
948          national character set.  This does not affect the type of data
949          stored, use a BINARY type for binary data.
950
951        :param collation: Optional, request a particular collation.  Must be
952          compatible with the national character set.
953
954        """
955        _StringType.__init__(self, **kwargs)
956        sqltypes.CHAR.__init__(self, length,
957                               kwargs.get('convert_unicode', False))
958
959    def get_col_spec(self):
960        return self._extend("CHAR(%(length)s)" % {'length' : self.length})
961
962
963class MSNVarChar(_StringType, sqltypes.String):
964    """MySQL NVARCHAR type.
965
966    For variable-length character data in the server's configured national
967    character set.
968    """
969
970    def __init__(self, length=None, **kwargs):
971        """Construct an NVARCHAR.
972
973        :param length: Maximum data length, in characters.
974
975        :param binary: Optional, use the default binary collation for the
976          national character set.  This does not affect the type of data
977          stored, use a BINARY type for binary data.
978
979        :param collation: Optional, request a particular collation.  Must be
980          compatible with the national character set.
981
982        """
983        kwargs['national'] = True
984        _StringType.__init__(self, **kwargs)
985        sqltypes.String.__init__(self, length,
986                                 kwargs.get('convert_unicode', False))
987
988    def get_col_spec(self):
989        # We'll actually generate the equiv. "NATIONAL VARCHAR" instead
990        # of "NVARCHAR".
991        return self._extend("VARCHAR(%(length)s)" % {'length': self.length})
992
993
994class MSNChar(_StringType, sqltypes.CHAR):
995    """MySQL NCHAR type.
996
997    For fixed-length character data in the server's configured national
998    character set.
999    """
1000
1001    def __init__(self, length=None, **kwargs):
1002        """Construct an NCHAR.  Arguments are:
1003
1004        :param length: Maximum data length, in characters.
1005
1006        :param binary: Optional, use the default binary collation for the
1007          national character set.  This does not affect the type of data
1008          stored, use a BINARY type for binary data.
1009
1010        :param collation: Optional, request a particular collation.  Must be
1011          compatible with the national character set.
1012
1013        """
1014        kwargs['national'] = True
1015        _StringType.__init__(self, **kwargs)
1016        sqltypes.CHAR.__init__(self, length,
1017                               kwargs.get('convert_unicode', False))
1018    def get_col_spec(self):
1019        # We'll actually generate the equiv. "NATIONAL CHAR" instead of "NCHAR".
1020        return self._extend("CHAR(%(length)s)" % {'length': self.length})
1021
1022
1023class _BinaryType(sqltypes.Binary):
1024    """Base for MySQL binary types."""
1025
1026    def get_col_spec(self):
1027        if self.length:
1028            return "BLOB(%d)" % self.length
1029        else:
1030            return "BLOB"
1031
1032    def result_processor(self, dialect):
1033        def process(value):
1034            if value is None:
1035                return None
1036            else:
1037                return util.buffer(value)
1038        return process
1039
1040class MSVarBinary(_BinaryType):
1041    """MySQL VARBINARY type, for variable length binary data."""
1042
1043    def __init__(self, length=None, **kw):
1044        """Construct a VARBINARY.  Arguments are:
1045
1046        :param length: Maximum data length, in characters.
1047
1048        """
1049        super(MSVarBinary, self).__init__(length, **kw)
1050
1051    def get_col_spec(self):
1052        if self.length:
1053            return "VARBINARY(%d)" % self.length
1054        else:
1055            return "BLOB"
1056
1057
1058class MSBinary(_BinaryType):
1059    """MySQL BINARY type, for fixed length binary data"""
1060
1061    def __init__(self, length=None, **kw):
1062        """Construct a BINARY.
1063
1064        This is a fixed length type, and short values will be right-padded
1065        with a server-version-specific pad value.
1066
1067        :param length: Maximum data length, in bytes.  If length is not
1068          specified, this will generate a BLOB.  This usage is deprecated.
1069
1070        """
1071        super(MSBinary, self).__init__(length, **kw)
1072
1073    def get_col_spec(self):
1074        if self.length:
1075            return "BINARY(%d)" % self.length
1076        else:
1077            return "BLOB"
1078
1079    def result_processor(self, dialect):
1080        def process(value):
1081            if value is None:
1082                return None
1083            else:
1084                return util.buffer(value)
1085        return process
1086
1087class MSBlob(_BinaryType):
1088    """MySQL BLOB type, for binary data up to 2^16 bytes"""
1089
1090    def __init__(self, length=None, **kw):
1091        """Construct a BLOB.  Arguments are:
1092
1093        :param length: Optional, if provided the server may optimize storage
1094          by substituting the smallest TEXT type sufficient to store
1095          ``length`` characters.
1096
1097        """
1098        super(MSBlob, self).__init__(length, **kw)
1099
1100    def get_col_spec(self):
1101        if self.length:
1102            return "BLOB(%d)" % self.length
1103        else:
1104            return "BLOB"
1105
1106    def result_processor(self, dialect):
1107        def process(value):
1108            if value is None:
1109                return None
1110            else:
1111                return util.buffer(value)
1112        return process
1113
1114    def __repr__(self):
1115        return "%s()" % self.__class__.__name__
1116
1117
1118class MSTinyBlob(MSBlob):
1119    """MySQL TINYBLOB type, for binary data up to 2^8 bytes."""
1120
1121    def get_col_spec(self):
1122        return "TINYBLOB"
1123
1124
1125class MSMediumBlob(MSBlob):
1126    """MySQL MEDIUMBLOB type, for binary data up to 2^24 bytes."""
1127
1128    def get_col_spec(self):
1129        return "MEDIUMBLOB"
1130
1131
1132class MSLongBlob(MSBlob):
1133    """MySQL LONGBLOB type, for binary data up to 2^32 bytes."""
1134
1135    def get_col_spec(self):
1136        return "LONGBLOB"
1137
1138
1139class MSEnum(MSString):
1140    """MySQL ENUM type."""
1141
1142    def __init__(self, *enums, **kw):
1143        """Construct an ENUM.
1144
1145        Example:
1146
1147          Column('myenum', MSEnum("foo", "bar", "baz"))
1148
1149        Arguments are:
1150
1151        :param enums: The range of valid values for this ENUM.  Values will be
1152          quoted when generating the schema according to the quoting flag (see
1153          below).
1154
1155        :param strict: Defaults to False: ensure that a given value is in this
1156          ENUM's range of permissible values when inserting or updating rows.
1157          Note that MySQL will not raise a fatal error if you attempt to store
1158          an out of range value- an alternate value will be stored instead.
1159          (See MySQL ENUM documentation.)
1160
1161        :param charset: Optional, a column-level character set for this string
1162          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
1163
1164        :param collation: Optional, a column-level collation for this string
1165          value.  Takes precedence to 'binary' short-hand.
1166
1167        :param ascii: Defaults to False: short-hand for the ``latin1``
1168          character set, generates ASCII in schema.
1169
1170        :param unicode: Defaults to False: short-hand for the ``ucs2``
1171          character set, generates UNICODE in schema.
1172
1173        :param binary: Defaults to False: short-hand, pick the binary
1174          collation type that matches the column's character set.  Generates
1175          BINARY in schema.  This does not affect the type of data stored,
1176          only the collation of character data.
1177
1178        :param quoting: Defaults to 'auto': automatically determine enum value
1179          quoting.  If all enum values are surrounded by the same quoting
1180          character, then use 'quoted' mode.  Otherwise, use 'unquoted' mode.
1181
1182          'quoted': values in enums are already quoted, they will be used
1183          directly when generating the schema.
1184
1185          'unquoted': values in enums are not quoted, they will be escaped and
1186          surrounded by single quotes when generating the schema.
1187
1188          Previous versions of this type always required manually quoted
1189          values to be supplied; future versions will always quote the string
1190          literals for you.  This is a transitional option.
1191
1192        """
1193        self.quoting = kw.pop('quoting', 'auto')
1194
1195        if self.quoting == 'auto':
1196            # What quoting character are we using?
1197            q = None
1198            for e in enums:
1199                if len(e) == 0:
1200                    self.quoting = 'unquoted'
1201                    break
1202                elif q is None:
1203                    q = e[0]
1204
1205                if e[0] != q or e[-1] != q:
1206                    self.quoting = 'unquoted'
1207                    break
1208            else:
1209                self.quoting = 'quoted'
1210
1211        if self.quoting == 'quoted':
1212            util.warn_pending_deprecation(
1213                'Manually quoting ENUM value literals is deprecated.  Supply '
1214                'unquoted values and use the quoting= option in cases of '
1215                'ambiguity.')
1216            strip_enums = []
1217            for a in enums:
1218                if a[0:1] == '"' or a[0:1] == "'":
1219                    # strip enclosing quotes and unquote interior
1220                    a = a[1:-1].replace(a[0] * 2, a[0])
1221                strip_enums.append(a)
1222            self.enums = strip_enums
1223        else:
1224            self.enums = list(enums)
1225
1226        self.strict = kw.pop('strict', False)
1227        length = max([len(v) for v in self.enums] + [0])
1228        super(MSEnum, self).__init__(length, **kw)
1229
1230    def bind_processor(self, dialect):
1231        super_convert = super(MSEnum, self).bind_processor(dialect)
1232        def process(value):
1233            if self.strict and value is not None and value not in self.enums:
1234                raise exc.InvalidRequestError('"%s" not a valid value for '
1235                                                     'this enum' % value)
1236            if super_convert:
1237                return super_convert(value)
1238            else:
1239                return value
1240        return process
1241
1242    def get_col_spec(self):
1243        quoted_enums = []
1244        for e in self.enums:
1245            quoted_enums.append("'%s'" % e.replace("'", "''"))
1246        return self._extend("ENUM(%s)" % ",".join(quoted_enums))
1247
1248class MSSet(MSString):
1249    """MySQL SET type."""
1250
1251    def __init__(self, *values, **kw):
1252        """Construct a SET.
1253
1254        Example::
1255
1256          Column('myset', MSSet("'foo'", "'bar'", "'baz'"))
1257
1258        Arguments are:
1259
1260        :param values: The range of valid values for this SET.  Values will be
1261          used exactly as they appear when generating schemas.  Strings must
1262          be quoted, as in the example above.  Single-quotes are suggested for
1263          ANSI compatibility and are required for portability to servers with
1264          ANSI_QUOTES enabled.
1265
1266        :param charset: Optional, a column-level character set for this string
1267          value.  Takes precedence to 'ascii' or 'unicode' short-hand.
1268
1269        :param collation: Optional, a column-level collation for this string
1270          value.  Takes precedence to 'binary' short-hand.
1271
1272        :param ascii: Defaults to False: short-hand for the ``latin1``
1273          character set, generates ASCII in schema.
1274
1275        :param unicode: Defaults to False: short-hand for the ``ucs2``
1276          character set, generates UNICODE in schema.
1277
1278        :param binary: Defaults to False: short-hand, pick the binary
1279          collation type that matches the column's character set.  Generates
1280          BINARY in schema.  This does not affect the type of data stored,
1281          only the collation of character data.
1282
1283        """
1284        self.__ddl_values = values
1285
1286        strip_values = []
1287        for a in values:
1288            if a[0:1] == '"' or a[0:1] == "'":
1289                # strip enclosing quotes and unquote interior
1290                a = a[1:-1].replace(a[0] * 2, a[0])
1291            strip_values.append(a)
1292
1293        self.values = strip_values
1294        length = max([len(v) for v in strip_values] + [0])
1295        super(MSSet, self).__init__(length, **kw)
1296
1297    def result_processor(self, dialect):
1298        def process(value):
1299            # The good news:
1300            #   No ',' quoting issues- commas aren't allowed in SET values
1301            # The bad news:
1302            #   Plenty of driver inconsistencies here.
1303            if isinstance(value, util.set_types):
1304                # ..some versions convert '' to an empty set
1305                if not value:
1306                    value.add('')
1307                # ..some return sets.Set, even for pythons that have __builtin__.set
1308                if not isinstance(value, set):
1309                    value = set(value)
1310                return value
1311            # ...and some versions return strings
1312            if value is not None:
1313                return set(value.split(','))
1314            else:
1315                return value
1316        return process
1317
1318    def bind_processor(self, dialect):
1319        super_convert = super(MSSet, self).bind_processor(dialect)
1320        def process(value):
1321            if value is None or isinstance(value, (int, long, basestring)):
1322                pass
1323            else:
1324                if None in value:
1325                    value = set(value)
1326                    value.remove(None)
1327                    value.add('')
1328                value = ','.join(value)
1329            if super_convert:
1330                return super_convert(value)
1331            else:
1332                return value
1333        return process
1334
1335    def get_col_spec(self):
1336        return self._extend("SET(%s)" % ",".join(self.__ddl_values))
1337
1338
1339class MSBoolean(sqltypes.Boolean):
1340    """MySQL BOOLEAN type."""
1341
1342    def get_col_spec(self):
1343        return "BOOL"
1344
1345    def result_processor(self, dialect):
1346        def process(value):
1347            if value is None:
1348                return None
1349            return value and True or False
1350        return process
1351
1352    def bind_processor(self, dialect):
1353        def process(value):
1354            if value is True:
1355                return 1
1356            elif value is False:
1357                return 0
1358            elif value is None:
1359                return None
1360            else:
1361                return value and True or False
1362        return process
1363
1364colspecs = {
1365    sqltypes.Integer: MSInteger,
1366    sqltypes.Smallinteger: MSSmallInteger,
1367    sqltypes.Numeric: MSNumeric,
1368    sqltypes.Float: MSFloat,
1369    sqltypes.DateTime: MSDateTime,
1370    sqltypes.Date: MSDate,
1371    sqltypes.Time: MSTime,
1372    sqltypes.String: MSString,
1373    sqltypes.Binary: MSBlob,
1374    sqltypes.Boolean: MSBoolean,
1375    sqltypes.Text: MSText,
1376    sqltypes.CHAR: MSChar,
1377    sqltypes.NCHAR: MSNChar,
1378    sqltypes.TIMESTAMP: MSTimeStamp,
1379    sqltypes.BLOB: MSBlob,
1380    MSDouble: MSDouble,
1381    MSReal: MSReal,
1382    _BinaryType: _BinaryType,
1383}
1384
1385# Everything 3.23 through 5.1 excepting OpenGIS types.
1386ischema_names = {
1387    'bigint': MSBigInteger,
1388    'binary': MSBinary,
1389    'bit': MSBit,
1390    'blob': MSBlob,
1391    'boolean':MSBoolean,
1392    'char': MSChar,
1393    'date': MSDate,
1394    'datetime': MSDateTime,
1395    'decimal': MSDecimal,
1396    'double': MSDouble,
1397    'enum': MSEnum,
1398    'fixed': MSDecimal,
1399    'float': MSFloat,
1400    'int': MSInteger,
1401    'integer': MSInteger,
1402    'longblob': MSLongBlob,
1403    'longtext': MSLongText,
1404    'mediumblob': MSMediumBlob,
1405    'mediumint': MSMediumInteger,
1406    'mediumtext': MSMediumText,
1407    'nchar': MSNChar,
1408    'nvarchar': MSNVarChar,
1409    'numeric': MSNumeric,
1410    'set': MSSet,
1411    'smallint': MSSmallInteger,
1412    'text': MSText,
1413    'time': MSTime,
1414    'timestamp': MSTimeStamp,
1415    'tinyblob': MSTinyBlob,
1416    'tinyint': MSTinyInteger,
1417    'tinytext': MSTinyText,
1418    'varbinary': MSVarBinary,
1419    'varchar': MSString,
1420    'year': MSYear,
1421}
1422
1423
1424class MySQLExecutionContext(default.DefaultExecutionContext):
1425    def post_exec(self):
1426        if self.compiled.isinsert and not self.executemany:
1427            if (not len(self._last_inserted_ids) or
1428                self._last_inserted_ids[0] is None):
1429                self._last_inserted_ids = ([self.cursor.lastrowid] +
1430                                           self._last_inserted_ids[1:])
1431        elif (not self.isupdate and not self.should_autocommit and
1432              self.statement and SET_RE.match(self.statement)):
1433            # This misses if a user forces autocommit on text('SET NAMES'),
1434            # which is probably a programming error anyhow.
1435            self.connection.info.pop(('mysql', 'charset'), None)
1436
1437    def should_autocommit_text(self, statement):
1438        return AUTOCOMMIT_RE.match(statement)
1439
1440
1441class MySQLDialect(default.DefaultDialect):
1442    """Details of the MySQL dialect.  Not used directly in application code."""
1443    name = 'mysql'
1444    supports_alter = True
1445    supports_unicode_statements = False
1446    # identifiers are 64, however aliases can be 255...
1447    max_identifier_length = 255
1448    supports_sane_rowcount = True
1449    default_paramstyle = 'format'
1450
1451    def __init__(self, use_ansiquotes=None, **kwargs):
1452        self.use_ansiquotes = use_ansiquotes
1453        default.DefaultDialect.__init__(self, **kwargs)
1454
1455    def dbapi(cls):
1456        import MySQLdb as mysql
1457        return mysql
1458    dbapi = classmethod(dbapi)
1459
1460    def create_connect_args(self, url):
1461        opts = url.translate_connect_args(database='db', username='user',
1462                                          password='passwd')
1463        opts.update(url.query)
1464
1465        util.coerce_kw_type(opts, 'compress', bool)
1466        util.coerce_kw_type(opts, 'connect_timeout', int)
1467        util.coerce_kw_type(opts, 'client_flag', int)
1468        util.coerce_kw_type(opts, 'local_infile', int)
1469        # Note: using either of the below will cause all strings to be returned
1470        # as Unicode, both in raw SQL operations and with column types like
1471        # String and MSString.
1472        util.coerce_kw_type(opts, 'use_unicode', bool)
1473        util.coerce_kw_type(opts, 'charset', str)
1474
1475        # Rich values 'cursorclass' and 'conv' are not supported via
1476        # query string.
1477
1478        ssl = {}
1479        for key in ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']:
1480            if key in opts:
1481                ssl[key[4:]] = opts[key]
1482                util.coerce_kw_type(ssl, key[4:], str)
1483                del opts[key]
1484        if ssl:
1485            opts['ssl'] = ssl
1486
1487        # FOUND_ROWS must be set in CLIENT_FLAGS to enable
1488        # supports_sane_rowcount.
1489        client_flag = opts.get('client_flag', 0)
1490        if self.dbapi is not None:
1491            try:
1492                import MySQLdb.constants.CLIENT as CLIENT_FLAGS
1493                client_flag |= CLIENT_FLAGS.FOUND_ROWS
1494            except:
1495                pass
1496            opts['client_flag'] = client_flag
1497        return [[], opts]
1498
1499    def type_descriptor(self, typeobj):
1500        return sqltypes.adapt_type(typeobj, colspecs)
1501
1502    def do_executemany(self, cursor, statement, parameters, context=None):
1503        rowcount = cursor.executemany(statement, parameters)
1504        if context is not None:
1505            context._rowcount = rowcount
1506
1507    def supports_unicode_statements(self):
1508        return True
1509
1510    def do_commit(self, connection):
1511        """Execute a COMMIT."""
1512
1513        # COMMIT/ROLLBACK were introduced in 3.23.15.
1514        # Yes, we have at least one user who has to talk to these old versions!
1515        #
1516        # Ignore commit/rollback if support isn't present, otherwise even basic
1517        # operations via autocommit fail.
1518        try:
1519            connection.commit()
1520        except:
1521            if self._server_version_info(connection) < (3, 23, 15):
1522                args = sys.exc_info()[1].args
1523                if args and args[0] == 1064:
1524                    return
1525            raise
1526
1527    def do_rollback(self, connection):
1528        """Execute a ROLLBACK."""
1529
1530        try:
1531            connection.rollback()
1532        except:
1533            if self._server_version_info(connection) < (3, 23, 15):
1534                args = sys.exc_info()[1].args
1535                if args and args[0] == 1064:
1536                    return
1537            raise
1538
1539    def do_begin_twophase(self, connection, xid):
1540        connection.execute("XA BEGIN %s", xid)
1541
1542    def do_prepare_twophase(self, connection, xid):
1543        connection.execute("XA END %s", xid)
1544        connection.execute("XA PREPARE %s", xid)
1545
1546    def do_rollback_twophase(self, connection, xid, is_prepared=True,
1547                             recover=False):
1548        if not is_prepared:
1549            connection.execute("XA END %s", xid)
1550        connection.execute("XA ROLLBACK %s", xid)
1551
1552    def do_commit_twophase(self, connection, xid, is_prepared=True,
1553                           recover=False):
1554        if not is_prepared:
1555            self.do_prepare_twophase(connection, xid)
1556        connection.execute("XA COMMIT %s", xid)
1557
1558    def do_recover_twophase(self, connection):
1559        resultset = connection.execute("XA RECOVER")
1560        return [row['data'][0:row['gtrid_length']] for row in resultset]
1561
1562    def do_ping(self, connection):
1563        connection.ping()
1564
1565    def is_disconnect(self, e):
1566        if isinstance(e, self.dbapi.OperationalError):
1567            return e.args[0] in (2006, 2013, 2014, 2045, 2055)
1568        elif isinstance(e, self.dbapi.InterfaceError):  # if underlying connection is closed, this is the error you get
1569            return "(0, '')" in str(e)
1570        else:
1571            return False
1572
1573    def get_default_schema_name(self, connection):
1574        return connection.execute('SELECT DATABASE()').scalar()
1575    get_default_schema_name = engine_base.connection_memoize(
1576        ('dialect', 'default_schema_name'))(get_default_schema_name)
1577
1578    def table_names(self, connection, schema):
1579        """Return a Unicode SHOW TABLES from a given schema."""
1580
1581        charset = self._detect_charset(connection)
1582        self._autoset_identifier_style(connection)
1583        rp = connection.execute("SHOW TABLES FROM %s" %
1584            self.identifier_preparer.quote_identifier(schema))
1585        return [row[0] for row in _compat_fetchall(rp, charset=charset)]
1586
1587    def has_table(self, connection, table_name, schema=None):
1588        # SHOW TABLE STATUS LIKE and SHOW TABLES LIKE do not function properly
1589        # on macosx (and maybe win?) with multibyte table names.
1590        #
1591        # TODO: if this is not a problem on win, make the strategy swappable
1592        # based on platform.  DESCRIBE is slower.
1593
1594        # [ticket:726]
1595        # full_name = self.identifier_preparer.format_table(table,
1596        #                                                   use_schema=True)
1597
1598        self._autoset_identifier_style(connection)
1599
1600        full_name = '.'.join(self.identifier_preparer._quote_free_identifiers(
1601            schema, table_name))
1602
1603        st = "DESCRIBE %s" % full_name
1604        rs = None
1605        try:
1606            try:
1607                rs = connection.execute(st)
1608                have = rs.rowcount > 0
1609                rs.close()
1610                return have
1611            except exc.SQLError, e:
1612                if e.orig.args[0] == 1146:
1613                    return False
1614                raise
1615        finally:
1616            if rs:
1617                rs.close()
1618
1619    def server_version_info(self, connection):
1620        """A tuple of the database server version.
1621
1622        Formats the remote server version as a tuple of version values,
1623        e.g. ``(5, 0, 44)``.  If there are strings in the version number
1624        they will be in the tuple too, so don't count on these all being
1625        ``int`` values.
1626
1627        This is a fast check that does not require a round trip.  It is also
1628        cached per-Connection.
1629        """
1630
1631        return self._server_version_info(connection.connection.connection)
1632    server_version_info = engine_base.connection_memoize(
1633        ('mysql', 'server_version_info'))(server_version_info)
1634
1635    def _server_version_info(self, dbapi_con):
1636        """Convert a MySQL-python server_info string into a tuple."""
1637
1638        version = []
1639        r = re.compile('[.\-]')
1640        for n in r.split(dbapi_con.get_server_info()):
1641            try:
1642                version.append(int(n))
1643            except ValueError:
1644                version.append(n)
1645        return tuple(version)
1646
1647    def reflecttable(self, connection, table, include_columns):
1648        """Load column definitions from the server."""
1649
1650        charset = self._detect_charset(connection)
1651        self._autoset_identifier_style(connection)
1652
1653        try:
1654            reflector = self.reflector
1655        except AttributeError:
1656            preparer = self.identifier_preparer
1657            if (self.server_version_info(connection) < (4, 1) and
1658                self.use_ansiquotes):
1659                # ANSI_QUOTES doesn't affect SHOW CREATE TABLE on < 4.1
1660                preparer = MySQLIdentifierPreparer(self)
1661
1662            self.reflector = reflector = MySQLSchemaReflector(preparer)
1663
1664        sql = self._show_create_table(connection, table, charset)
1665        if sql.startswith('CREATE ALGORITHM'):
1666            # Adapt views to something table-like.
1667            columns = self._describe_table(connection, table, charset)
1668            sql = reflector._describe_to_create(table, columns)
1669
1670        self._adjust_casing(connection, table)
1671
1672        return reflector.reflect(connection, table, sql, charset,
1673                                 only=include_columns)
1674
1675    def _adjust_casing(self, connection, table, charset=None):
1676        """Adjust Table name to the server case sensitivity, if needed."""
1677
1678        casing = self._detect_casing(connection)
1679
1680        # For winxx database hosts.  TODO: is this really needed?
1681        if casing == 1 and table.name != table.name.lower():
1682            table.name = table.name.lower()
1683            lc_alias = schema._get_table_key(table.name, table.schema)
1684            table.metadata.tables[lc_alias] = table
1685
1686
1687    def _detect_charset(self, connection):
1688        """Sniff out the character set in use for connection results."""
1689
1690        # Allow user override, won't sniff if force_charset is set.
1691        if ('mysql', 'force_charset') in connection.info:
1692            return connection.info[('mysql', 'force_charset')]
1693
1694        # Note: MySQL-python 1.2.1c7 seems to ignore changes made
1695        # on a connection via set_character_set()
1696        if self.server_version_info(connection) < (4, 1, 0):
1697            try:
1698                return connection.connection.character_set_name()
1699            except AttributeError:
1700                # < 1.2.1 final MySQL-python drivers have no charset support.
1701                # a query is needed.
1702                pass
1703
1704        # Prefer 'character_set_results' for the current connection over the
1705        # value in the driver.  SET NAMES or individual variable SETs will
1706        # change the charset without updating the driver's view of the world.
1707        #
1708        # If it's decided that issuing that sort of SQL leaves you SOL, then
1709        # this can prefer the driver value.
1710        rs = connection.execute("SHOW VARIABLES LIKE 'character_set%%'")
1711        opts = dict([(row[0], row[1]) for row in _compat_fetchall(rs)])
1712
1713        if 'character_set_results' in opts:
1714            return opts['character_set_results']
1715        try:
1716            return connection.connection.character_set_name()
1717        except AttributeError:
1718            # Still no charset on < 1.2.1 final...
1719            if 'character_set' in opts:
1720                return opts['character_set']
1721            else:
1722                util.warn(
1723                    "Could not detect the connection character set with this "
1724                    "combination of MySQL server and MySQL-python. "
1725                    "MySQL-python >= 1.2.2 is recommended.  Assuming latin1.")
1726                return 'latin1'
1727    _detect_charset = engine_base.connection_memoize(
1728        ('mysql', 'charset'))(_detect_charset)
1729
1730
1731    def _detect_casing(self, connection):
1732        """Sniff out identifier case sensitivity.
1733
1734        Cached per-connection. This value can not change without a server
1735        restart.
1736
1737        """
1738        # http://dev.mysql.com/doc/refman/5.0/en/name-case-sensitivity.html
1739
1740        charset = self._detect_charset(connection)
1741        row = _compat_fetchone(connection.execute(
1742            "SHOW VARIABLES LIKE 'lower_case_table_names'"),
1743                               charset=charset)
1744        if not row:
1745            cs = 0
1746        else:
1747            # 4.0.15 returns OFF or ON according to [ticket:489]
1748            # 3.23 doesn't, 4.0.27 doesn't..
1749            if row[1] == 'OFF':
1750                cs = 0
1751            elif row[1] == 'ON':
1752                cs = 1
1753            else:
1754                cs = int(row[1])
1755            row.close()
1756        return cs
1757    _detect_casing = engine_base.connection_memoize(
1758        ('mysql', 'lower_case_table_names'))(_detect_casing)
1759
1760    def _detect_collations(self, connection):
1761        """Pull the active COLLATIONS list from the server.
1762
1763        Cached per-connection.
1764        """
1765
1766        collations = {}
1767        if self.server_version_info(connection) < (4, 1, 0):
1768            pass
1769        else:
1770            charset = self._detect_charset(connection)
1771            rs = connection.execute('SHOW COLLATION')
1772            for row in _compat_fetchall(rs, charset):
1773                collations[row[0]] = row[1]
1774        return collations
1775    _detect_collations = engine_base.connection_memoize(
1776        ('mysql', 'collations'))(_detect_collations)
1777
1778    def use_ansiquotes(self, useansi):
1779        self._use_ansiquotes = useansi
1780        if useansi:
1781            self.preparer = MySQLANSIIdentifierPreparer
1782        else:
1783            self.preparer = MySQLIdentifierPreparer
1784        # icky
1785        if hasattr(self, 'identifier_preparer'):
1786            self.identifier_preparer = self.preparer(self)
1787        if hasattr(self, 'reflector'):
1788            del self.reflector
1789
1790    use_ansiquotes = property(lambda s: s._use_ansiquotes, use_ansiquotes,
1791                              doc="True if ANSI_QUOTES is in effect.")
1792
1793    def _autoset_identifier_style(self, connection, charset=None):
1794        """Detect and adjust for the ANSI_QUOTES sql mode.
1795
1796        If the dialect's use_ansiquotes is unset, query the server's sql mode
1797        and reset the identifier style.
1798
1799        Note that this currently *only* runs during reflection.  Ideally this
1800        would run the first time a connection pool connects to the database,
1801        but the infrastructure for that is not yet in place.
1802        """
1803
1804        if self.use_ansiquotes is not None:
1805            return
1806
1807        row = _compat_fetchone(
1808            connection.execute("SHOW VARIABLES LIKE 'sql_mode'"),
1809                               charset=charset)
1810        if not row:
1811            mode = ''
1812        else:
1813            mode = row[1] or ''
1814            # 4.0
1815            if mode.isdigit():
1816                mode_no = int(mode)
1817                mode = (mode_no | 4 == mode_no) and 'ANSI_QUOTES' or ''
1818
1819        self.use_ansiquotes = 'ANSI_QUOTES' in mode
1820
1821    def _show_create_table(self, connection, table, charset=None,
1822                           full_name=None):
1823        """Run SHOW CREATE TABLE for a ``Table``."""
1824
1825        if full_name is None:
1826            full_name = self.identifier_preparer.format_table(table)
1827        st = "SHOW CREATE TABLE %s" % full_name
1828
1829        rp = None
1830        try:
1831            try:
1832                rp = connection.execute(st)
1833            except exc.SQLError, e:
1834                if e.orig.args[0] == 1146:
1835                    raise exc.NoSuchTableError(full_name)
1836                else:
1837                    raise
1838            row = _compat_fetchone(rp, charset=charset)
1839            if not row:
1840                raise exc.NoSuchTableError(full_name)
1841            return row[1].strip()
1842        finally:
1843            if rp:
1844                rp.close()
1845
1846        return sql
1847
1848    def _describe_table(self, connection, table, charset=None,
1849                             full_name=None):
1850        """Run DESCRIBE for a ``Table`` and return processed rows."""
1851
1852        if full_name is None:
1853            full_name = self.identifier_preparer.format_table(table)
1854        st = "DESCRIBE %s" % full_name
1855
1856        rp, rows = None, None
1857        try:
1858            try:
1859                rp = connection.execute(st)
1860            except exc.SQLError, e:
1861                if e.orig.args[0] == 1146:
1862                    raise exc.NoSuchTableError(full_name)
1863                else:
1864                    raise
1865            rows = _compat_fetchall(rp, charset=charset)
1866        finally:
1867            if rp:
1868                rp.close()
1869        return rows
1870
1871class _MySQLPythonRowProxy(object):
1872    """Return consistent column values for all versions of MySQL-python.
1873
1874    Smooth over data type issues (esp. with alpha driver versions) and
1875    normalize strings as Unicode regardless of user-configured driver
1876    encoding settings.
1877    """
1878
1879    # Some MySQL-python versions can return some columns as
1880    # sets.Set(['value']) (seriously) but thankfully that doesn't
1881    # seem to come up in DDL queries.
1882
1883    def __init__(self, rowproxy, charset):
1884        self.rowproxy = rowproxy
1885        self.charset = charset
1886    def __getitem__(self, index):
1887        item = self.rowproxy[index]
1888        if isinstance(item, _array):
1889            item = item.tostring()
1890        if self.charset and isinstance(item, str):
1891            return item.decode(self.charset)
1892        else:
1893            return item
1894    def __getattr__(self, attr):
1895        item = getattr(self.rowproxy, attr)
1896        if isinstance(item, _array):
1897            item = item.tostring()
1898        if self.charset and isinstance(item, str):
1899            return item.decode(self.charset)
1900        else:
1901            return item
1902
1903
1904class MySQLCompiler(compiler.DefaultCompiler):
1905    operators = compiler.DefaultCompiler.operators.copy()
1906    operators.update({
1907        sql_operators.concat_op: lambda x, y: "concat(%s, %s)" % (x, y),
1908        sql_operators.mod: '%%',
1909        sql_operators.match_op: lambda x, y: "MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" % (x, y)
1910    })
1911    functions = compiler.DefaultCompiler.functions.copy()
1912    functions.update ({
1913        sql_functions.random: 'rand%(expr)s',
1914        "utc_timestamp":"UTC_TIMESTAMP"
1915        })
1916
1917    extract_map = compiler.DefaultCompiler.extract_map.copy()
1918    extract_map.update ({
1919        'milliseconds': 'millisecond',
1920    })
1921
1922    def visit_typeclause(self, typeclause):
1923        type_ = typeclause.type.dialect_impl(self.dialect)
1924        if isinstance(type_, MSInteger):
1925            if getattr(type_, 'unsigned', False):
1926                return 'UNSIGNED INTEGER'
1927            else:
1928                return 'SIGNED INTEGER'
1929        elif isinstance(type_, (MSDecimal, MSDateTime, MSDate, MSTime)):
1930            return type_.get_col_spec()
1931        elif isinstance(type_, MSText):
1932            return 'CHAR'
1933        elif (isinstance(type_, _StringType) and not
1934              isinstance(type_, (MSEnum, MSSet))):
1935            if getattr(type_, 'length'):
1936                return 'CHAR(%s)' % type_.length
1937            else:
1938                return 'CHAR'
1939        elif isinstance(type_, _BinaryType):
1940            return 'BINARY'
1941        elif isinstance(type_, MSNumeric):
1942            return type_.get_col_spec().replace('NUMERIC', 'DECIMAL')
1943        elif isinstance(type_, MSTimeStamp):
1944            return 'DATETIME'
1945        elif isinstance(type_, (MSDateTime, MSDate, MSTime)):
1946            return type_.get_col_spec()
1947        else:
1948            return None
1949
1950    def visit_cast(self, cast, **kwargs):
1951        # No cast until 4, no decimals until 5.
1952        type_ = self.process(cast.typeclause)
1953        if type_ is None:
1954            return self.process(cast.clause)
1955
1956        return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
1957
1958
1959    def post_process_text(self, text):
1960        if '%%' in text:
1961            util.warn("The SQLAlchemy MySQLDB dialect now automatically escapes '%' in text() expressions to '%%'.")
1962        return text.replace('%', '%%')
1963
1964    def get_select_precolumns(self, select):
1965        if isinstance(select._distinct, basestring):
1966            return select._distinct.upper() + " "
1967        elif select._distinct:
1968            return "DISTINCT "
1969        else:
1970            return ""
1971
1972    def visit_join(self, join, asfrom=False, **kwargs):
1973        # 'JOIN ... ON ...' for inner joins isn't available until 4.0.
1974        # Apparently < 3.23.17 requires theta joins for inner joins
1975        # (but not outer).  Not generating these currently, but
1976        # support can be added, preferably after dialects are
1977        # refactored to be version-sensitive.
1978        return ''.join(
1979            (self.process(join.left, asfrom=True),
1980             (join.isouter and " LEFT OUTER JOIN " or " INNER JOIN "),
1981             self.process(join.right, asfrom=True),
1982             " ON ",
1983             self.process(join.onclause)))
1984
1985    def for_update_clause(self, select):
1986        if select.for_update == 'read':
1987            return ' LOCK IN SHARE MODE'
1988        else:
1989            return super(MySQLCompiler, self).for_update_clause(select)
1990
1991    def limit_clause(self, select):
1992        # MySQL supports:
1993        #   LIMIT <limit>
1994        #   LIMIT <offset>, <limit>
1995        # and in server versions > 3.3:
1996        #   LIMIT <limit> OFFSET <offset>
1997        # The latter is more readable for offsets but we're stuck with the
1998        # former until we can refine dialects by server revision.
1999
2000        limit, offset = select._limit, select._offset
2001
2002        if (limit, offset) == (None, None):
2003            return ''
2004        elif offset is not None:
2005            # As suggested by the MySQL docs, need to apply an
2006            # artificial limit if one wasn't provided
2007            if limit is None:
2008                limit = 18446744073709551615
2009            return ' \n LIMIT %s, %s' % (offset, limit)
2010        else:
2011            # No offset provided, so just use the limit
2012            return ' \n LIMIT %s' % (limit,)
2013
2014    def visit_update(self, update_stmt):
2015        self.stack.append({'from': set([update_stmt.table])})
2016
2017        self.isupdate = True
2018        colparams = self._get_colparams(update_stmt)
2019
2020        text = "UPDATE " + self.preparer.format_table(update_stmt.table) + " SET " + ', '.join(["%s=%s" % (self.preparer.format_column(c[0]), c[1]) for c in colparams])
2021
2022        if update_stmt._whereclause:
2023            text += " WHERE " + self.process(update_stmt._whereclause)
2024
2025        limit = update_stmt.kwargs.get('mysql_limit', None)
2026        if limit:
2027            text += " LIMIT %s" % limit
2028
2029        self.stack.pop(-1)
2030
2031        return text
2032
2033# ug.  "InnoDB needs indexes on foreign keys and referenced keys [...].
2034#       Starting with MySQL 4.1.2, these indexes are created automatically.
2035#       In older versions, the indexes must be created explicitly or the
2036#       creation of foreign key constraints fails."
2037
2038class MySQLSchemaGenerator(compiler.SchemaGenerator):
2039    def get_column_specification(self, column, first_pk=False):
2040        """Builds column DDL."""
2041
2042        colspec = [self.preparer.format_column(column),
2043                   column.type.dialect_impl(self.dialect).get_col_spec()]
2044
2045        default = self.get_column_default_string(column)
2046        if default is not None:
2047            colspec.append('DEFAULT ' + default)
2048
2049        if not column.nullable:
2050            colspec.append('NOT NULL')
2051
2052        if column.primary_key and column.autoincrement:
2053            try:
2054                first = [c for c in column.table.primary_key.columns
2055                         if (c.autoincrement and
2056                             isinstance(c.type, sqltypes.Integer) and
2057                             not c.foreign_keys)].pop(0)
2058                if column is first:
2059                    colspec.append('AUTO_INCREMENT')
2060            except IndexError:
2061                pass
2062
2063        return ' '.join(colspec)
2064
2065    def post_create_table(self, table):
2066        """Build table-level CREATE options like ENGINE and COLLATE."""
2067
2068        table_opts = []
2069        for k in table.kwargs:
2070            if k.startswith('mysql_'):
2071                opt = k[6:].upper()
2072                joiner = '='
2073                if opt in ('TABLESPACE', 'DEFAULT CHARACTER SET',
2074                           'CHARACTER SET', 'COLLATE'):
2075                    joiner = ' '
2076
2077                table_opts.append(joiner.join((opt, table.kwargs[k])))
2078        return ' '.join(table_opts)
2079
2080
2081class MySQLSchemaDropper(compiler.SchemaDropper):
2082    def visit_index(self, index):
2083        self.append("\nDROP INDEX %s ON %s" %
2084                    (self.preparer.quote(self._validate_identifier(index.name, False), index.quote),
2085                     self.preparer.format_table(index.table)))
2086        self.execute()
2087
2088    def drop_foreignkey(self, constraint):
2089        self.append("ALTER TABLE %s DROP FOREIGN KEY %s" %
2090                    (self.preparer.format_table(constraint.table),
2091                     self.preparer.format_constraint(constraint)))
2092        self.execute()
2093
2094
2095class MySQLSchemaReflector(object):
2096    """Parses SHOW CREATE TABLE output."""
2097
2098    def __init__(self, identifier_preparer):
2099        """Construct a MySQLSchemaReflector.
2100
2101        identifier_preparer
2102          An ANSIIdentifierPreparer type, used to determine the identifier
2103          quoting style in effect.
2104        """
2105
2106        self.preparer = identifier_preparer
2107        self._prep_regexes()
2108
2109    def reflect(self, connection, table, show_create, charset, only=None):
2110        """Parse MySQL SHOW CREATE TABLE and fill in a ''Table''.
2111
2112        show_create
2113          Unicode output of SHOW CREATE TABLE
2114
2115        table
2116          A ''Table'', to be loaded with Columns, Indexes, etc.
2117          table.name will be set if not already
2118
2119        charset
2120          FIXME, some constructed values (like column defaults)
2121          currently can't be Unicode.  ''charset'' will convert them
2122          into the connection character set.
2123
2124        only
2125           An optional sequence of column names.  If provided, only
2126           these columns will be reflected, and any keys or constraints
2127           that include columns outside this set will also be omitted.
2128           That means that if ``only`` includes only one column in a
2129           2 part primary key, the entire primary key will be omitted.
2130        """
2131
2132        keys, constraints = [], []
2133
2134        if only:
2135            only = set(only)
2136
2137        for line in re.split(r'\r?\n', show_create):
2138            if line.startswith('  ' + self.preparer.initial_quote):
2139                self._add_column(table, line, charset, only)
2140            # a regular table options line
2141            elif line.startswith(') '):
2142                self._set_options(table, line)
2143            # an ANSI-mode table options line
2144            elif line == ')':
2145                pass
2146            elif line.startswith('CREATE '):
2147                self._set_name(table, line)
2148            # Not present in real reflection, but may be if loading from a file.
2149            elif not line:
2150                pass
2151            else:
2152                type_, spec = self.parse_constraints(line)
2153                if type_ is None:
2154                    util.warn("Unknown schema content: %r" % line)
2155                elif type_ == 'key':
2156                    keys.append(spec)
2157                elif type_ == 'constraint':
2158                    constraints.append(spec)
2159                else:
2160                    pass
2161
2162        self._set_keys(table, keys, only)
2163        self._set_constraints(table, constraints, connection, only)
2164
2165    def _set_name(self, table, line):
2166        """Override a Table name with the reflected name.
2167
2168        table
2169          A ``Table``
2170
2171        line
2172          The first line of SHOW CREATE TABLE output.
2173        """
2174
2175        # Don't override by default.
2176        if table.name is None:
2177            table.name = self.parse_name(line)
2178
2179    def _add_column(self, table, line, charset, only=None):
2180        spec = self.parse_column(line)
2181        if not spec:
2182            util.warn("Unknown column definition %r" % line)
2183            return
2184        if not spec['full']:
2185            util.warn("Incomplete reflection of column definition %r" % line)
2186
2187        name, type_, args, notnull = \
2188              spec['name'], spec['coltype'], spec['arg'], spec['notnull']
2189
2190        if only and name not in only:
2191            self.logger.info("Omitting reflected column %s.%s" %
2192                             (table.name, name))
2193            return
2194
2195        # Convention says that TINYINT(1) columns == BOOLEAN
2196        if type_ == 'tinyint' and args == '1':
2197            type_ = 'boolean'
2198            args = None
2199
2200        try:
2201            col_type = ischema_names[type_]
2202        except KeyError:
2203            util.warn("Did not recognize type '%s' of column '%s'" %
2204                      (type_, name))
2205            col_type = sqltypes.NullType
2206
2207        # Column type positional arguments eg. varchar(32)
2208        if args is None or args == '':
2209            type_args = []
2210        elif args[0] == "'" and args[-1] == "'":
2211            type_args = self._re_csv_str.findall(args)
2212        else:
2213            type_args = [int(v) for v in self._re_csv_int.findall(args)]
2214
2215        # Column type keyword options
2216        type_kw = {}
2217        for kw in ('unsigned', 'zerofill'):
2218            if spec.get(kw, False):
2219                type_kw[kw] = True
2220        for kw in ('charset', 'collate'):
2221            if spec.get(kw, False):
2222                type_kw[kw] = spec[kw]
2223
2224        if type_ == 'enum':
2225            type_kw['quoting'] = 'quoted'
2226
2227        type_instance = col_type(*type_args, **type_kw)
2228
2229        col_args, col_kw = [], {}
2230
2231        # NOT NULL
2232        if spec.get('notnull', False):
2233            col_kw['nullable'] = False
2234
2235        # AUTO_INCREMENT
2236        if spec.get('autoincr', False):
2237            col_kw['autoincrement'] = True
2238        elif issubclass(col_type, sqltypes.Integer):
2239            col_kw['autoincrement'] = False
2240
2241        # DEFAULT
2242        default = spec.get('default', None)
2243        if default is not None and default != 'NULL':
2244            # Defaults should be in the native charset for the moment
2245            default = default.encode(charset)
2246            if type_ == 'timestamp':
2247                # can't be NULL for TIMESTAMPs
2248                if (default[0], default[-1]) != ("'", "'"):
2249                    default = sql.text(default)
2250            else:
2251                default = default[1:-1]
2252            col_args.append(schema.DefaultClause(default))
2253
2254        table.append_column(schema.Column(name, type_instance,
2255                                          *col_args, **col_kw))
2256
2257    def _set_keys(self, table, keys, only):
2258        """Add ``Index`` and ``PrimaryKeyConstraint`` items to a ``Table``.
2259
2260        Most of the information gets dropped here- more is reflected than
2261        the schema objects can currently represent.
2262
2263        table
2264          A ``Table``
2265
2266        keys
2267          A sequence of key specifications produced by `constraints`
2268
2269        only
2270          Optional `set` of column names.  If provided, keys covering
2271          columns not in this set will be omitted.
2272        """
2273
2274        for spec in keys:
2275            flavor = spec['type']
2276            col_names = [s[0] for s in spec['columns']]
2277
2278            if only and not set(col_names).issubset(only):
2279                if flavor is None:
2280                    flavor = 'index'
2281                self.logger.info(
2282                    "Omitting %s KEY for (%s), key covers ommitted columns." %
2283                    (flavor, ', '.join(col_names)))
2284                continue
2285
2286            constraint = False
2287            if flavor == 'PRIMARY':
2288                key = schema.PrimaryKeyConstraint()
2289                constraint = True
2290            elif flavor == 'UNIQUE':
2291                key = schema.Index(spec['name'], unique=True)
2292            elif flavor in (None, 'FULLTEXT', 'SPATIAL'):
2293                key = schema.Index(spec['name'])
2294            else:
2295                self.logger.info(
2296                    "Converting unknown KEY type %s to a plain KEY" % flavor)
2297                key = schema.Index(spec['name'])
2298
2299            for col in [table.c[name] for name in col_names]:
2300                key.append_column(col)
2301
2302            if constraint:
2303                table.append_constraint(key)
2304
2305    def _set_constraints(self, table, constraints, connection, only):
2306        """Apply constraints to a ``Table``."""
2307
2308        default_schema = None
2309
2310        for spec in constraints:
2311            # only FOREIGN KEYs
2312            ref_name = spec['table'][-1]
2313            ref_schema = len(spec['table']) > 1 and spec['table'][-2] or table.schema
2314
2315            if not ref_schema:
2316                if default_schema is None:
2317                    default_schema = connection.dialect.get_default_schema_name(
2318                        connection)
2319                if table.schema == default_schema:
2320                    ref_schema = table.schema
2321
2322            loc_names = spec['local']
2323            if only and not set(loc_names).issubset(only):
2324                self.logger.info(
2325                    "Omitting FOREIGN KEY for (%s), key covers ommitted "
2326                    "columns." % (', '.join(loc_names)))
2327                continue
2328
2329            ref_key = schema._get_table_key(ref_name, ref_schema)
2330            if ref_key in table.metadata.tables:
2331                ref_table = table.metadata.tables[ref_key]
2332            else:
2333                ref_table = schema.Table(
2334                    ref_name, table.metadata, schema=ref_schema,
2335                    autoload=True, autoload_with=connection)
2336
2337            ref_names = spec['foreign']
2338
2339            if ref_schema:
2340                refspec = [".".join([ref_schema, ref_name, column]) for column in ref_names]
2341            else:
2342                refspec = [".".join([ref_name, column]) for column in ref_names]
2343
2344            con_kw = {}
2345            for opt in ('name', 'onupdate', 'ondelete'):
2346                if spec.get(opt, False):
2347                    con_kw[opt] = spec[opt]
2348
2349            key = schema.ForeignKeyConstraint(loc_names, refspec, link_to_name=True, **con_kw)
2350            table.append_constraint(key)
2351
2352    def _set_options(self, table, line):
2353        """Apply safe reflected table options to a ``Table``.
2354
2355        table
2356          A ``Table``
2357
2358        line
2359          The final line of SHOW CREATE TABLE output.
2360        """
2361
2362        options = self.parse_table_options(line)
2363        for nope in ('auto_increment', 'data_directory', 'index_directory'):
2364            options.pop(nope, None)
2365
2366        for opt, val in options.items():
2367            table.kwargs['mysql_%s' % opt] = val
2368
2369    def _prep_regexes(self):
2370        """Pre-compile regular expressions."""
2371
2372        self._re_columns = []
2373        self._pr_options = []
2374        self._re_options_util = {}
2375
2376        _final = self.preparer.final_quote
2377
2378        quotes = dict(zip(('iq', 'fq', 'esc_fq'),
2379                          [re.escape(s) for s in
2380                           (self.preparer.initial_quote,
2381                            _final,
2382                            self.preparer._escape_identifier(_final))]))
2383
2384        self._pr_name = _pr_compile(
2385            r'^CREATE (?:\w+ +)?TABLE +'
2386            r'%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +\($' % quotes,
2387            self.preparer._unescape_identifier)
2388
2389        # `col`,`col2`(32),`col3`(15) DESC
2390        #
2391        # Note: ASC and DESC aren't reflected, so we'll punt...
2392        self._re_keyexprs = _re_compile(
2393            r'(?:'
2394            r'(?:%(iq)s((?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)'
2395            r'(?:\((\d+)\))?(?=\,|$))+' % quotes)
2396
2397        # 'foo' or 'foo','bar' or 'fo,o','ba''a''r'
2398        self._re_csv_str = _re_compile(r'\x27(?:\x27\x27|[^\x27])*\x27')
2399
2400        # 123 or 123,456
2401        self._re_csv_int = _re_compile(r'\d+')
2402
2403
2404        # `colname` <type> [type opts]
2405        #  (NOT NULL | NULL)
2406        #   DEFAULT ('value' | CURRENT_TIMESTAMP...)
2407        #   COMMENT 'comment'
2408        #  COLUMN_FORMAT (FIXED|DYNAMIC|DEFAULT)
2409        #  STORAGE (DISK|MEMORY)
2410        self._re_column = _re_compile(
2411            r'  '
2412            r'%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +'
2413            r'(?P<coltype>\w+)'
2414            r'(?:\((?P<arg>(?:\d+|\d+,\d+|'
2415              r'(?:\x27(?:\x27\x27|[^\x27])*\x27,?)+))\))?'
2416            r'(?: +(?P<unsigned>UNSIGNED))?'
2417            r'(?: +(?P<zerofill>ZEROFILL))?'
2418            r'(?: +CHARACTER SET +(?P<charset>\w+))?'
2419            r'(?: +COLLATE +(P<collate>\w+))?'
2420            r'(?: +(?P<notnull>NOT NULL))?'
2421            r'(?: +DEFAULT +(?P<default>'
2422              r'(?:NULL|\x27(?:\x27\x27|[^\x27])*\x27|\w+)'
2423              r'(?:ON UPDATE \w+)?'
2424            r'))?'
2425            r'(?: +(?P<autoincr>AUTO_INCREMENT))?'
2426            r'(?: +COMMENT +(P<comment>(?:\x27\x27|[^\x27])+))?'
2427            r'(?: +COLUMN_FORMAT +(?P<colfmt>\w+))?'
2428            r'(?: +STORAGE +(?P<storage>\w+))?'
2429            r'(?: +(?P<extra>.*))?'
2430            r',?$'
2431            % quotes
2432            )
2433
2434        # Fallback, try to parse as little as possible
2435        self._re_column_loose = _re_compile(
2436            r'  '
2437            r'%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +'
2438            r'(?P<coltype>\w+)'
2439            r'(?:\((?P<arg>(?:\d+|\d+,\d+|\x27(?:\x27\x27|[^\x27])+\x27))\))?'
2440            r'.*?(?P<notnull>NOT NULL)?'
2441            % quotes
2442            )
2443
2444        # (PRIMARY|UNIQUE|FULLTEXT|SPATIAL) INDEX `name` (USING (BTREE|HASH))?
2445        # (`col` (ASC|DESC)?, `col` (ASC|DESC)?)
2446        # KEY_BLOCK_SIZE size | WITH PARSER name
2447        self._re_key = _re_compile(
2448            r'  '
2449            r'(?:(?P<type>\S+) )?KEY'
2450            r'(?: +%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?'
2451            r'(?: +USING +(?P<using_pre>\S+))?'
2452            r' +\((?P<columns>.+?)\)'
2453            r'(?: +USING +(?P<using_post>\S+))?'
2454            r'(?: +KEY_BLOCK_SIZE +(?P<keyblock>\S+))?'
2455            r'(?: +WITH PARSER +(?P<parser>\S+))?'
2456            r',?$'
2457            % quotes
2458            )
2459
2460        # CONSTRAINT `name` FOREIGN KEY (`local_col`)
2461        # REFERENCES `remote` (`remote_col`)
2462        # MATCH FULL | MATCH PARTIAL | MATCH SIMPLE
2463        # ON DELETE CASCADE ON UPDATE RESTRICT
2464        #
2465        # unique constraints come back as KEYs
2466        kw = quotes.copy()
2467        kw['on'] = 'RESTRICT|CASCASDE|SET NULL|NOACTION'
2468        self._re_constraint = _re_compile(
2469            r'  '
2470            r'CONSTRAINT +'
2471            r'%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +'
2472            r'FOREIGN KEY +'
2473            r'\((?P<local>[^\)]+?)\) REFERENCES +'
2474            r'(?P<table>%(iq)s[^%(fq)s]+%(fq)s(?:\.%(iq)s[^%(fq)s]+%(fq)s)?) +'
2475            r'\((?P<foreign>[^\)]+?)\)'
2476            r'(?: +(?P<match>MATCH \w+))?'
2477            r'(?: +ON DELETE (?P<ondelete>%(on)s))?'
2478            r'(?: +ON UPDATE (?P<onupdate>%(on)s))?'
2479            % kw
2480            )
2481
2482        # PARTITION
2483        #
2484        # punt!
2485        self._re_partition = _re_compile(
2486            r'  '
2487            r'(?:SUB)?PARTITION')
2488
2489        # Table-level options (COLLATE, ENGINE, etc.)
2490        for option in ('ENGINE', 'TYPE', 'AUTO_INCREMENT',
2491                       'AVG_ROW_LENGTH', 'CHARACTER SET',
2492                       'DEFAULT CHARSET', 'CHECKSUM',
2493                       'COLLATE', 'DELAY_KEY_WRITE', 'INSERT_METHOD',
2494                       'MAX_ROWS', 'MIN_ROWS', 'PACK_KEYS', 'ROW_FORMAT',
2495                       'KEY_BLOCK_SIZE'):
2496            self._add_option_word(option)
2497
2498        for option in (('COMMENT', 'DATA_DIRECTORY', 'INDEX_DIRECTORY',
2499                        'PASSWORD', 'CONNECTION')):
2500            self._add_option_string(option)
2501
2502        self._add_option_regex('UNION', r'\([^\)]+\)')
2503        self._add_option_regex('TABLESPACE', r'.*? STORAGE DISK')
2504        self._add_option_regex('RAID_TYPE',
2505          r'\w+\s+RAID_CHUNKS\s*\=\s*\w+RAID_CHUNKSIZE\s*=\s*\w+')
2506        self._re_options_util['='] = _re_compile(r'\s*=\s*$')
2507
2508    def _add_option_string(self, directive):
2509        regex = (r'(?P<directive>%s\s*(?:=\s*)?)'
2510                 r'(?:\x27.(?P<val>.*?)\x27(?!\x27)\x27)' %
2511                 re.escape(directive))
2512        self._pr_options.append(
2513            _pr_compile(regex, lambda v: v.replace("''", "'")))
2514
2515    def _add_option_word(self, directive):
2516        regex = (r'(?P<directive>%s\s*(?:=\s*)?)'
2517                 r'(?P<val>\w+)' % re.escape(directive))
2518        self._pr_options.append(_pr_compile(regex))
2519
2520    def _add_option_regex(self, directive, regex):
2521        regex = (r'(?P<directive>%s\s*(?:=\s*)?)'
2522                 r'(?P<val>%s)' % (re.escape(directive), regex))
2523        self._pr_options.append(_pr_compile(regex))
2524
2525
2526    def parse_name(self, line):
2527        """Extract the table name.
2528
2529        line
2530          The first line of SHOW CREATE TABLE
2531        """
2532
2533        regex, cleanup = self._pr_name
2534        m = regex.match(line)
2535        if not m:
2536            return None
2537        return cleanup(m.group('name'))
2538
2539    def parse_column(self, line):
2540        """Extract column details.
2541
2542        Falls back to a 'minimal support' variant if full parse fails.
2543
2544        line
2545          Any column-bearing line from SHOW CREATE TABLE
2546        """
2547
2548        m = self._re_column.match(line)
2549        if m:
2550            spec = m.groupdict()
2551            spec['full'] = True
2552            return spec
2553        m = self._re_column_loose.match(line)
2554        if m:
2555            spec = m.groupdict()
2556            spec['full'] = False
2557            return spec
2558        return None
2559
2560    def parse_constraints(self, line):
2561        """Parse a KEY or CONSTRAINT line.
2562
2563        line
2564          A line of SHOW CREATE TABLE output
2565        """
2566
2567        # KEY
2568        m = self._re_key.match(line)
2569        if m:
2570            spec = m.groupdict()
2571            # convert columns into name, length pairs
2572            spec['columns'] = self._parse_keyexprs(spec['columns'])
2573            return 'key', spec
2574
2575        # CONSTRAINT
2576        m = self._re_constraint.match(line)
2577        if m:
2578            spec = m.groupdict()
2579            spec['table'] = \
2580              self.preparer.unformat_identifiers(spec['table'])
2581            spec['local'] = [c[0]
2582                             for c in self._parse_keyexprs(spec['local'])]
2583            spec['foreign'] = [c[0]
2584                               for c in self._parse_keyexprs(spec['foreign'])]
2585            return 'constraint', spec
2586
2587        # PARTITION and SUBPARTITION
2588        m = self._re_partition.match(line)
2589        if m:
2590            # Punt!
2591            return 'partition', line
2592
2593        # No match.
2594        return (None, line)
2595
2596    def parse_table_options(self, line):
2597        """Build a dictionary of all reflected table-level options.
2598
2599        line
2600          The final line of SHOW CREATE TABLE output.
2601        """
2602
2603        options = {}
2604
2605        if not line or line == ')':
2606            return options
2607
2608        r_eq_trim = self._re_options_util['=']
2609
2610        for regex, cleanup in self._pr_options:
2611            m = regex.search(line)
2612            if not m:
2613                continue
2614            directive, value = m.group('directive'), m.group('val')
2615            directive = r_eq_trim.sub('', directive).lower()
2616            if cleanup:
2617                value = cleanup(value)
2618            options[directive] = value
2619
2620        return options
2621
2622    def _describe_to_create(self, table, columns):
2623        """Re-format DESCRIBE output as a SHOW CREATE TABLE string.
2624
2625        DESCRIBE is a much simpler reflection and is sufficient for
2626        reflecting views for runtime use.  This method formats DDL
2627        for columns only- keys are omitted.
2628
2629        `columns` is a sequence of DESCRIBE or SHOW COLUMNS 6-tuples.
2630        SHOW FULL COLUMNS FROM rows must be rearranged for use with
2631        this function.
2632        """
2633
2634        buffer = []
2635        for row in columns:
2636            (name, col_type, nullable, default, extra) = \
2637                   [row[i] for i in (0, 1, 2, 4, 5)]
2638
2639            line = [' ']
2640            line.append(self.preparer.quote_identifier(name))
2641            line.append(col_type)
2642            if not nullable:
2643                line.append('NOT NULL')
2644            if default:
2645                if 'auto_increment' in default:
2646                    pass
2647                elif (col_type.startswith('timestamp') and
2648                      default.startswith('C')):
2649                    line.append('DEFAULT')
2650                    line.append(default)
2651                elif default == 'NULL':
2652                    line.append('DEFAULT')
2653                    line.append(default)
2654                else:
2655                    line.append('DEFAULT')
2656                    line.append("'%s'" % default.replace("'", "''"))
2657            if extra:
2658                line.append(extra)
2659
2660            buffer.append(' '.join(line))
2661
2662        return ''.join([('CREATE TABLE %s (\n' %
2663                         self.preparer.quote_identifier(table.name)),
2664                        ',\n'.join(buffer),
2665                        '\n) '])
2666
2667    def _parse_keyexprs(self, identifiers):
2668        """Unpack '"col"(2),"col" ASC'-ish strings into components."""
2669
2670        return self._re_keyexprs.findall(identifiers)
2671
2672log.class_logger(MySQLSchemaReflector)
2673
2674
2675class _MySQLIdentifierPreparer(compiler.IdentifierPreparer):
2676    """MySQL-specific schema identifier configuration."""
2677
2678    reserved_words = RESERVED_WORDS
2679
2680    def __init__(self, dialect, **kw):
2681        super(_MySQLIdentifierPreparer, self).__init__(dialect, **kw)
2682
2683    def _quote_free_identifiers(self, *ids):
2684        """Unilaterally identifier-quote any number of strings."""
2685
2686        return tuple([self.quote_identifier(i) for i in ids if i is not None])
2687
2688
2689class MySQLIdentifierPreparer(_MySQLIdentifierPreparer):
2690    """Traditional MySQL-specific schema identifier configuration."""
2691
2692    def __init__(self, dialect):
2693        super(MySQLIdentifierPreparer, self).__init__(dialect, initial_quote="`")
2694
2695    def _escape_identifier(self, value):
2696        return value.replace('`', '``')
2697
2698    def _unescape_identifier(self, value):
2699        return value.replace('``', '`')
2700
2701
2702class MySQLANSIIdentifierPreparer(_MySQLIdentifierPreparer):
2703    """ANSI_QUOTES MySQL schema identifier configuration."""
2704
2705    pass
2706
2707
2708def _compat_fetchall(rp, charset=None):
2709    """Proxy result rows to smooth over MySQL-Python driver inconsistencies."""
2710
2711    return [_MySQLPythonRowProxy(row, charset) for row in rp.fetchall()]
2712
2713def _compat_fetchone(rp, charset=None):
2714    """Proxy a result row to smooth over MySQL-Python driver inconsistencies."""
2715
2716    return _MySQLPythonRowProxy(rp.fetchone(), charset)
2717
2718def _pr_compile(regex, cleanup=None):
2719    """Prepare a 2-tuple of compiled regex and callable."""
2720
2721    return (_re_compile(regex), cleanup)
2722
2723def _re_compile(regex):
2724    """Compile a string to regex, I and UNICODE."""
2725
2726    return re.compile(regex, re.I | re.UNICODE)
2727
2728dialect = MySQLDialect
2729dialect.statement_compiler = MySQLCompiler
2730dialect.schemagenerator = MySQLSchemaGenerator
2731dialect.schemadropper = MySQLSchemaDropper
2732dialect.execution_ctx_cls = MySQLExecutionContext
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。