| 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 | |
|---|
| 10 | Overview |
|---|
| 11 | -------- |
|---|
| 12 | |
|---|
| 13 | For normal SQLAlchemy usage, importing this module is unnecessary. It will be |
|---|
| 14 | loaded on-demand when a MySQL connection is needed. The generic column types |
|---|
| 15 | like :class:`~sqlalchemy.String` and :class:`~sqlalchemy.Integer` will |
|---|
| 16 | automatically be adapted to the optimal matching MySQL column type. |
|---|
| 17 | |
|---|
| 18 | But if you would like to use one of the MySQL-specific or enhanced column |
|---|
| 19 | types when creating tables with your :class:`~sqlalchemy.Table` definitions, |
|---|
| 20 | then 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 | |
|---|
| 29 | All standard MySQL column types are supported. The OpenGIS types are |
|---|
| 30 | available for use via table reflection but have no special support or mapping |
|---|
| 31 | to Python classes. If you're using these types and have opinions about how |
|---|
| 32 | OpenGIS can be smartly integrated into SQLAlchemy please join the mailing |
|---|
| 33 | list! |
|---|
| 34 | |
|---|
| 35 | Supported Versions and Features |
|---|
| 36 | ------------------------------- |
|---|
| 37 | |
|---|
| 38 | SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0, |
|---|
| 39 | with capabilities increasing with more modern servers. |
|---|
| 40 | |
|---|
| 41 | Versions 4.1 and higher support the basic SQL functionality that SQLAlchemy |
|---|
| 42 | uses in the ORM and SQL expressions. These versions pass the applicable tests |
|---|
| 43 | in the suite 100%. No heroic measures are taken to work around major missing |
|---|
| 44 | SQL features- if your server version does not support sub-selects, for |
|---|
| 45 | example, they won't work in SQLAlchemy either. |
|---|
| 46 | |
|---|
| 47 | Currently, the only DB-API driver supported is `MySQL-Python` (also referred to |
|---|
| 48 | as `MySQLdb`). Either 1.2.1 or 1.2.2 are recommended. The alpha, beta and |
|---|
| 49 | gamma releases of 1.2.1 and 1.2.2 should be avoided. Support for Jython and |
|---|
| 50 | IronPython is planned. |
|---|
| 51 | |
|---|
| 52 | ===================================== =============== |
|---|
| 53 | Feature Minimum Version |
|---|
| 54 | ===================================== =============== |
|---|
| 55 | sqlalchemy.orm 4.1.1 |
|---|
| 56 | Table Reflection 3.23.x |
|---|
| 57 | DDL Generation 4.1.1 |
|---|
| 58 | utf8/Full Unicode Connections 4.1.1 |
|---|
| 59 | Transactions 3.23.15 |
|---|
| 60 | Two-Phase Transactions 5.0.3 |
|---|
| 61 | Nested Transactions 5.0.3 |
|---|
| 62 | ===================================== =============== |
|---|
| 63 | |
|---|
| 64 | See the official MySQL documentation for detailed information about features |
|---|
| 65 | supported in any given server release. |
|---|
| 66 | |
|---|
| 67 | Character Sets |
|---|
| 68 | -------------- |
|---|
| 69 | |
|---|
| 70 | Many MySQL server installations default to a ``latin1`` encoding for client |
|---|
| 71 | connections. All data sent through the connection will be converted into |
|---|
| 72 | ``latin1``, even if you have ``utf8`` or another character set on your tables |
|---|
| 73 | and columns. With versions 4.1 and higher, you can change the connection |
|---|
| 74 | character set either through server configuration or by including the |
|---|
| 75 | ``charset`` parameter in the URL used for ``create_engine``. The ``charset`` |
|---|
| 76 | option is passed through to MySQL-Python and has the side-effect of also |
|---|
| 77 | enabling ``use_unicode`` in the driver by default. For regular encoded |
|---|
| 78 | strings, 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 | |
|---|
| 86 | Storage Engines |
|---|
| 87 | --------------- |
|---|
| 88 | |
|---|
| 89 | Most MySQL server installations have a default table type of ``MyISAM``, a |
|---|
| 90 | non-transactional table type. During a transaction, non-transactional storage |
|---|
| 91 | engines do not participate and continue to store table changes in autocommit |
|---|
| 92 | mode. For fully atomic transactions, all participating tables must use a |
|---|
| 93 | transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc. |
|---|
| 94 | |
|---|
| 95 | Storage engines can be elected when creating tables in SQLAlchemy by supplying |
|---|
| 96 | a ``mysql_engine='whatever'`` to the ``Table`` constructor. Any MySQL table |
|---|
| 97 | creation 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 | |
|---|
| 105 | Keys |
|---|
| 106 | ---- |
|---|
| 107 | |
|---|
| 108 | Not all MySQL storage engines support foreign keys. For ``MyISAM`` and |
|---|
| 109 | similar engines, the information loaded by table reflection will not include |
|---|
| 110 | foreign 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 | |
|---|
| 118 | When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT``` on |
|---|
| 119 | an 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 | |
|---|
| 130 | You can disable this behavior by supplying ``autoincrement=False`` to the |
|---|
| 131 | :class:`~sqlalchemy.Column`. This flag can also be used to enable |
|---|
| 132 | auto-increment on a secondary column in a multi-column key for some storage |
|---|
| 133 | engines:: |
|---|
| 134 | |
|---|
| 135 | Table('mytable', metadata, |
|---|
| 136 | Column('gid', Integer, primary_key=True, autoincrement=False), |
|---|
| 137 | Column('id', Integer, primary_key=True) |
|---|
| 138 | ) |
|---|
| 139 | |
|---|
| 140 | SQL Mode |
|---|
| 141 | -------- |
|---|
| 142 | |
|---|
| 143 | MySQL SQL modes are supported. Modes that enable ``ANSI_QUOTES`` (such as |
|---|
| 144 | ``ANSI``) require an engine option to modify SQLAlchemy's quoting style. |
|---|
| 145 | When using an ANSI-quoting mode, supply ``use_ansiquotes=True`` when |
|---|
| 146 | creating your ``Engine``:: |
|---|
| 147 | |
|---|
| 148 | create_engine('mysql://localhost/test', use_ansiquotes=True) |
|---|
| 149 | |
|---|
| 150 | This is an engine-wide option and is not toggleable on a per-connection basis. |
|---|
| 151 | SQLAlchemy does not presume to ``SET sql_mode`` for you with this option. For |
|---|
| 152 | the best performance, set the quoting style server-wide in ``my.cnf`` or by |
|---|
| 153 | supplying ``--sql-mode`` to ``mysqld``. You can also use a |
|---|
| 154 | :class:`sqlalchemy.pool.Pool` listener hook to issue a ``SET SESSION |
|---|
| 155 | sql_mode='...'`` on connect to configure each connection. |
|---|
| 156 | |
|---|
| 157 | If you do not specify ``use_ansiquotes``, the regular MySQL quoting style is |
|---|
| 158 | used by default. |
|---|
| 159 | |
|---|
| 160 | If you do issue a ``SET sql_mode`` through SQLAlchemy, the dialect must be |
|---|
| 161 | updated if the quoting style is changed. Again, this change will affect all |
|---|
| 162 | connections:: |
|---|
| 163 | |
|---|
| 164 | connection.execute('SET sql_mode="ansi"') |
|---|
| 165 | connection.dialect.use_ansiquotes = True |
|---|
| 166 | |
|---|
| 167 | MySQL SQL Extensions |
|---|
| 168 | -------------------- |
|---|
| 169 | |
|---|
| 170 | Many of the MySQL SQL extensions are handled through SQLAlchemy's generic |
|---|
| 171 | function and operator support:: |
|---|
| 172 | |
|---|
| 173 | table.select(table.c.password==func.md5('plaintext')) |
|---|
| 174 | table.select(table.c.username.op('regexp')('^[a-d]')) |
|---|
| 175 | |
|---|
| 176 | And of course any valid MySQL statement can be executed as a string as well. |
|---|
| 177 | |
|---|
| 178 | Some limited direct support for MySQL extensions to SQL is currently |
|---|
| 179 | available. |
|---|
| 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 | |
|---|
| 189 | Troubleshooting |
|---|
| 190 | --------------- |
|---|
| 191 | |
|---|
| 192 | If you have problems that seem server related, first check that you are |
|---|
| 193 | using the most recent stable MySQL-Python package available. The Database |
|---|
| 194 | Notes page on the wiki at http://www.sqlalchemy.org is a good resource for |
|---|
| 195 | timely information affecting MySQL in SQLAlchemy. |
|---|
| 196 | |
|---|
| 197 | """ |
|---|
| 198 | |
|---|
| 199 | import datetime, decimal, inspect, re, sys |
|---|
| 200 | from array import array as _array |
|---|
| 201 | |
|---|
| 202 | from sqlalchemy import exc, log, schema, sql, util |
|---|
| 203 | from sqlalchemy.sql import operators as sql_operators |
|---|
| 204 | from sqlalchemy.sql import functions as sql_functions |
|---|
| 205 | from sqlalchemy.sql import compiler |
|---|
| 206 | |
|---|
| 207 | from sqlalchemy.engine import base as engine_base, default |
|---|
| 208 | from 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 | |
|---|
| 221 | RESERVED_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 | |
|---|
| 263 | AUTOCOMMIT_RE = re.compile( |
|---|
| 264 | r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER|LOAD +DATA|REPLACE)', |
|---|
| 265 | re.I | re.UNICODE) |
|---|
| 266 | SET_RE = re.compile( |
|---|
| 267 | r'\s*SET\s+(?:(?:GLOBAL|SESSION)\s+)?\w', |
|---|
| 268 | re.I | re.UNICODE) |
|---|
| 269 | |
|---|
| 270 | |
|---|
| 271 | class _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 | |
|---|
| 288 | class _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 | |
|---|
| 344 | class 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 | |
|---|
| 387 | class 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 | |
|---|
| 417 | class 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 | |
|---|
| 456 | class 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 | |
|---|
| 486 | class 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 | |
|---|
| 522 | class 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 | |
|---|
| 553 | class 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 | |
|---|
| 578 | class 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 | |
|---|
| 604 | class 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 | |
|---|
| 633 | class 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 | |
|---|
| 660 | class 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 | |
|---|
| 695 | class MSDateTime(sqltypes.DateTime): |
|---|
| 696 | """MySQL DATETIME type.""" |
|---|
| 697 | |
|---|
| 698 | def get_col_spec(self): |
|---|
| 699 | return "DATETIME" |
|---|
| 700 | |
|---|
| 701 | |
|---|
| 702 | class MSDate(sqltypes.Date): |
|---|
| 703 | """MySQL DATE type.""" |
|---|
| 704 | |
|---|
| 705 | def get_col_spec(self): |
|---|
| 706 | return "DATE" |
|---|
| 707 | |
|---|
| 708 | |
|---|
| 709 | class 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 | |
|---|
| 724 | class 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 | |
|---|
| 747 | class 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 | |
|---|
| 759 | class 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 | |
|---|
| 801 | class 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 | |
|---|
| 835 | class 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 | |
|---|
| 868 | class 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 | |
|---|
| 901 | class 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 | |
|---|
| 939 | class 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 | |
|---|
| 963 | class 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 | |
|---|
| 994 | class 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 | |
|---|
| 1023 | class _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 | |
|---|
| 1040 | class 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 | |
|---|
| 1058 | class 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 | |
|---|
| 1087 | class 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 | |
|---|
| 1118 | class 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 | |
|---|
| 1125 | class 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 | |
|---|
| 1132 | class 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 | |
|---|
| 1139 | class 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 | |
|---|
| 1248 | class 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 | |
|---|
| 1339 | class 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 | |
|---|
| 1364 | colspecs = { |
|---|
| 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. |
|---|
| 1386 | ischema_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 | |
|---|
| 1424 | class 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 | |
|---|
| 1441 | class 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 | |
|---|
| 1871 | class _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 | |
|---|
| 1904 | class 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 | |
|---|
| 2038 | class 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 | |
|---|
| 2081 | class 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 | |
|---|
| 2095 | class 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 | |
|---|
| 2672 | log.class_logger(MySQLSchemaReflector) |
|---|
| 2673 | |
|---|
| 2674 | |
|---|
| 2675 | class _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 | |
|---|
| 2689 | class 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 | |
|---|
| 2702 | class MySQLANSIIdentifierPreparer(_MySQLIdentifierPreparer): |
|---|
| 2703 | """ANSI_QUOTES MySQL schema identifier configuration.""" |
|---|
| 2704 | |
|---|
| 2705 | pass |
|---|
| 2706 | |
|---|
| 2707 | |
|---|
| 2708 | def _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 | |
|---|
| 2713 | def _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 | |
|---|
| 2718 | def _pr_compile(regex, cleanup=None): |
|---|
| 2719 | """Prepare a 2-tuple of compiled regex and callable.""" |
|---|
| 2720 | |
|---|
| 2721 | return (_re_compile(regex), cleanup) |
|---|
| 2722 | |
|---|
| 2723 | def _re_compile(regex): |
|---|
| 2724 | """Compile a string to regex, I and UNICODE.""" |
|---|
| 2725 | |
|---|
| 2726 | return re.compile(regex, re.I | re.UNICODE) |
|---|
| 2727 | |
|---|
| 2728 | dialect = MySQLDialect |
|---|
| 2729 | dialect.statement_compiler = MySQLCompiler |
|---|
| 2730 | dialect.schemagenerator = MySQLSchemaGenerator |
|---|
| 2731 | dialect.schemadropper = MySQLSchemaDropper |
|---|
| 2732 | dialect.execution_ctx_cls = MySQLExecutionContext |
|---|