~ubuntu-branches/debian/sid/sqlalchemy/sid

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/dialects/mysql/base.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ożarowski
  • Date: 2011-03-27 14:22:50 UTC
  • mfrom: (16.1.9 experimental)
  • Revision ID: james.westby@ubuntu.com-20110327142250-aip46dv6a3r2jwvs
Tags: 0.6.6-2
Upload to unstable

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- fill-column: 78 -*-
2
1
# mysql/base.py
3
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
4
 
# and Jason Kirtland.
 
2
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
5
3
#
6
4
# This module is part of SQLAlchemy and is released under
7
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
41
39
----------
42
40
 
43
41
See the API documentation on individual drivers for details on connecting.
44
 
    
45
 
Data Types
46
 
----------
47
 
 
48
 
All of MySQL's standard types are supported.   These can also be specified within 
49
 
table metadata, for the purpose of issuing CREATE TABLE statements
50
 
which include MySQL-specific extensions.   The types are available
51
 
from the module, as in::
52
 
 
53
 
    from sqlalchemy.dialects import mysql
54
 
 
55
 
    Table('mytable', metadata,
56
 
          Column('id', Integer, primary_key=True),
57
 
          Column('ittybittyblob', mysql.TINYBLOB),
58
 
          Column('biggy', mysql.BIGINT(unsigned=True)))
59
 
 
60
 
See the API documentation on specific column types for further details.
61
42
 
62
43
Connection Timeouts
63
44
-------------------
254
235
        self.unsigned = kw.pop('unsigned', False)
255
236
        self.zerofill = kw.pop('zerofill', False)
256
237
        super(_NumericType, self).__init__(**kw)
257
 
        
 
238
 
258
239
class _FloatType(_NumericType, sqltypes.Float):
259
240
    def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
260
241
        if isinstance(self, (REAL, DOUBLE)) and \
293
274
        self.binary = binary
294
275
        self.national = national
295
276
        super(_StringType, self).__init__(**kw)
296
 
        
 
277
 
297
278
    def __repr__(self):
298
279
        attributes = inspect.getargspec(self.__init__)[0][1:]
299
280
        attributes.extend(inspect.getargspec(_StringType.__init__)[0][1:])
310
291
 
311
292
class NUMERIC(_NumericType, sqltypes.NUMERIC):
312
293
    """MySQL NUMERIC type."""
313
 
    
 
294
 
314
295
    __visit_name__ = 'NUMERIC'
315
 
    
 
296
 
316
297
    def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
317
298
        """Construct a NUMERIC.
318
299
 
334
315
 
335
316
class DECIMAL(_NumericType, sqltypes.DECIMAL):
336
317
    """MySQL DECIMAL type."""
337
 
    
 
318
 
338
319
    __visit_name__ = 'DECIMAL'
339
 
    
 
320
 
340
321
    def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
341
322
        """Construct a DECIMAL.
342
323
 
356
337
        super(DECIMAL, self).__init__(precision=precision, scale=scale,
357
338
                                      asdecimal=asdecimal, **kw)
358
339
 
359
 
    
 
340
 
360
341
class DOUBLE(_FloatType):
361
342
    """MySQL DOUBLE type."""
362
343
 
557
538
 
558
539
    def result_processor(self, dialect, coltype):
559
540
        """Convert a MySQL's 64 bit, variable length binary string to a long.
560
 
        
 
541
 
561
542
        TODO: this is MySQL-db, pyodbc specific.  OurSQL and mysqlconnector
562
543
        already do this, so this logic should be moved to those dialects.
563
 
        
 
544
 
564
545
        """
565
 
        
 
546
 
566
547
        def process(value):
567
548
            if value is not None:
568
549
                v = 0L
729
710
        """
730
711
        super(LONGTEXT, self).__init__(**kwargs)
731
712
 
732
 
    
 
713
 
733
714
class VARCHAR(_StringType, sqltypes.VARCHAR):
734
715
    """MySQL VARCHAR type, for variable-length character data."""
735
716
 
817
798
    __visit_name__ = 'NCHAR'
818
799
 
819
800
    def __init__(self, length=None, **kwargs):
820
 
        """Construct an NCHAR.  Arguments are:
 
801
        """Construct an NCHAR.
821
802
 
822
803
        :param length: Maximum data length, in characters.
823
804
 
837
818
 
838
819
class TINYBLOB(sqltypes._Binary):
839
820
    """MySQL TINYBLOB type, for binary data up to 2^8 bytes."""
840
 
    
 
821
 
841
822
    __visit_name__ = 'TINYBLOB'
842
823
 
843
824
class MEDIUMBLOB(sqltypes._Binary):
862
843
 
863
844
          Column('myenum', MSEnum("foo", "bar", "baz"))
864
845
 
865
 
        Arguments are:
866
 
 
867
846
        :param enums: The range of valid values for this ENUM.  Values will be
868
847
          quoted when generating the schema according to the quoting flag (see
869
848
          below).
907
886
 
908
887
        """
909
888
        self.quoting = kw.pop('quoting', 'auto')
910
 
        
 
889
 
911
890
        if self.quoting == 'auto' and len(enums):
912
891
            # What quoting character are we using?
913
892
            q = None
940
919
        kw.pop('native_enum', None)
941
920
        _StringType.__init__(self, length=length, **kw)
942
921
        sqltypes.Enum.__init__(self, *enums)
943
 
    
 
922
 
944
923
    @classmethod
945
924
    def _strip_enums(cls, enums):
946
925
        strip_enums = []
950
929
                a = a[1:-1].replace(a[0] * 2, a[0])
951
930
            strip_enums.append(a)
952
931
        return strip_enums
953
 
        
 
932
 
954
933
    def bind_processor(self, dialect):
955
934
        super_convert = super(ENUM, self).bind_processor(dialect)
956
935
        def process(value):
975
954
 
976
955
          Column('myset', MSSet("'foo'", "'bar'", "'baz'"))
977
956
 
978
 
        Arguments are:
979
 
 
980
957
        :param values: The range of valid values for this SET.  Values will be
981
958
          used exactly as they appear when generating schemas.  Strings must
982
959
          be quoted, as in the example above.  Single-quotes are suggested for
1140
1117
    extract_map.update ({
1141
1118
        'milliseconds': 'millisecond',
1142
1119
    })
1143
 
    
 
1120
 
1144
1121
    def visit_random_func(self, fn, **kw):
1145
1122
        return "rand%s" % self.function_argspec(fn)
1146
 
    
 
1123
 
1147
1124
    def visit_utc_timestamp_func(self, fn, **kw):
1148
1125
        return "UTC_TIMESTAMP"
1149
 
    
 
1126
 
1150
1127
    def visit_sysdate_func(self, fn, **kw):
1151
1128
        return "SYSDATE()"
1152
 
        
 
1129
 
1153
1130
    def visit_concat_op(self, binary, **kw):
1154
1131
        return "concat(%s, %s)" % (self.process(binary.left), self.process(binary.right))
1155
 
        
 
1132
 
1156
1133
    def visit_match_op(self, binary, **kw):
1157
1134
        return "MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" % (self.process(binary.left), self.process(binary.right))
1158
1135
 
1189
1166
        # No cast until 4, no decimals until 5.
1190
1167
        if not self.dialect._supports_cast:
1191
1168
            return self.process(cast.clause)
1192
 
        
 
1169
 
1193
1170
        type_ = self.process(cast.typeclause)
1194
1171
        if type_ is None:
1195
1172
            return self.process(cast.clause)
1201
1178
        if self.dialect._backslash_escapes:
1202
1179
            value = value.replace('\\', '\\\\')
1203
1180
        return value
1204
 
        
 
1181
 
1205
1182
    def get_select_precolumns(self, select):
1206
1183
        if isinstance(select._distinct, basestring):
1207
1184
            return select._distinct.upper() + " "
1281
1258
    def create_table_constraints(self, table):
1282
1259
        """Get table constraints."""
1283
1260
        constraint_string = super(MySQLDDLCompiler, self).create_table_constraints(table)
1284
 
        
 
1261
 
1285
1262
        is_innodb = table.kwargs.has_key('mysql_engine') and \
1286
1263
                    table.kwargs['mysql_engine'].lower() == 'innodb'
1287
1264
 
1294
1271
                constraint_string += ", \n\t"
1295
1272
            constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \
1296
1273
                            self.preparer.format_column(auto_inc_column))
1297
 
        
 
1274
 
1298
1275
        return constraint_string
1299
1276
 
1300
1277
 
1308
1285
        default = self.get_column_default_string(column)
1309
1286
        if default is not None:
1310
1287
            colspec.append('DEFAULT ' + default)
1311
 
        
 
1288
 
1312
1289
        is_timestamp = isinstance(column.type, sqltypes.TIMESTAMP)
1313
1290
        if not column.nullable and not is_timestamp:
1314
1291
            colspec.append('NOT NULL')
1356
1333
 
1357
1334
    def visit_drop_index(self, drop):
1358
1335
        index = drop.element
1359
 
        
 
1336
 
1360
1337
        return "\nDROP INDEX %s ON %s" % \
1361
 
                    (self.preparer.quote(self._validate_identifier(index.name, False), index.quote),
 
1338
                    (self.preparer.quote(self._index_identifier(index.name), index.quote),
1362
1339
                     self.preparer.format_table(index.table))
1363
1340
 
1364
1341
    def visit_drop_constraint(self, drop):
1397
1374
        COLLATE annotations and MySQL specific extensions.
1398
1375
 
1399
1376
        """
1400
 
        
 
1377
 
1401
1378
        def attr(name):
1402
1379
            return getattr(type_, name, defaults.get(name))
1403
 
            
 
1380
 
1404
1381
        if attr('charset'):
1405
1382
            charset = 'CHARACTER SET %s' % attr('charset')
1406
1383
        elif attr('ascii'):
1423
1400
                             if c is not None])
1424
1401
        return ' '.join([c for c in (spec, charset, collation)
1425
1402
                         if c is not None])
1426
 
    
 
1403
 
1427
1404
    def _mysql_type(self, type_):
1428
1405
        return isinstance(type_, (_StringType, _NumericType))
1429
 
    
 
1406
 
1430
1407
    def visit_NUMERIC(self, type_):
1431
1408
        if type_.precision is None:
1432
1409
            return self._extend_numeric(type_, "NUMERIC")
1458
1435
                                 'scale' : type_.scale})
1459
1436
        else:
1460
1437
            return self._extend_numeric(type_, 'REAL')
1461
 
    
 
1438
 
1462
1439
    def visit_FLOAT(self, type_):
1463
1440
        if self._mysql_type(type_) and type_.scale is not None and type_.precision is not None:
1464
1441
            return self._extend_numeric(type_, "FLOAT(%s, %s)" % (type_.precision, type_.scale))
1466
1443
            return self._extend_numeric(type_, "FLOAT(%s)" % (type_.precision,))
1467
1444
        else:
1468
1445
            return self._extend_numeric(type_, "FLOAT")
1469
 
    
 
1446
 
1470
1447
    def visit_INTEGER(self, type_):
1471
1448
        if self._mysql_type(type_) and type_.display_width is not None:
1472
1449
            return self._extend_numeric(type_, "INTEGER(%(display_width)s)" % {'display_width': type_.display_width})
1473
1450
        else:
1474
1451
            return self._extend_numeric(type_, "INTEGER")
1475
 
        
 
1452
 
1476
1453
    def visit_BIGINT(self, type_):
1477
1454
        if self._mysql_type(type_) and type_.display_width is not None:
1478
1455
            return self._extend_numeric(type_, "BIGINT(%(display_width)s)" % {'display_width': type_.display_width})
1479
1456
        else:
1480
1457
            return self._extend_numeric(type_, "BIGINT")
1481
 
    
 
1458
 
1482
1459
    def visit_MEDIUMINT(self, type_):
1483
1460
        if self._mysql_type(type_) and type_.display_width is not None:
1484
1461
            return self._extend_numeric(type_, "MEDIUMINT(%(display_width)s)" % {'display_width': type_.display_width})
1502
1479
            return "BIT(%s)" % type_.length
1503
1480
        else:
1504
1481
            return "BIT"
1505
 
    
 
1482
 
1506
1483
    def visit_DATETIME(self, type_):
1507
1484
        return "DATETIME"
1508
1485
 
1520
1497
            return "YEAR"
1521
1498
        else:
1522
1499
            return "YEAR(%s)" % type_.display_width
1523
 
    
 
1500
 
1524
1501
    def visit_TEXT(self, type_):
1525
1502
        if type_.length:
1526
1503
            return self._extend_string(type_, {}, "TEXT(%d)" % type_.length)
1527
1504
        else:
1528
1505
            return self._extend_string(type_, {}, "TEXT")
1529
 
        
 
1506
 
1530
1507
    def visit_TINYTEXT(self, type_):
1531
1508
        return self._extend_string(type_, {}, "TINYTEXT")
1532
1509
 
1533
1510
    def visit_MEDIUMTEXT(self, type_):
1534
1511
        return self._extend_string(type_, {}, "MEDIUMTEXT")
1535
 
    
 
1512
 
1536
1513
    def visit_LONGTEXT(self, type_):
1537
1514
        return self._extend_string(type_, {}, "LONGTEXT")
1538
 
    
 
1515
 
1539
1516
    def visit_VARCHAR(self, type_):
1540
1517
        if type_.length:
1541
1518
            return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
1542
1519
        else:
1543
1520
            raise exc.InvalidRequestError("VARCHAR requires a length when rendered on MySQL")
1544
 
    
 
1521
 
1545
1522
    def visit_CHAR(self, type_):
1546
1523
        if type_.length:
1547
1524
            return self._extend_string(type_, {}, "CHAR(%(length)s)" % {'length' : type_.length})
1548
1525
        else:
1549
1526
            return self._extend_string(type_, {}, "CHAR")
1550
 
            
 
1527
 
1551
1528
    def visit_NVARCHAR(self, type_):
1552
1529
        # We'll actually generate the equiv. "NATIONAL VARCHAR" instead
1553
1530
        # of "NVARCHAR".
1555
1532
            return self._extend_string(type_, {'national':True}, "VARCHAR(%(length)s)" % {'length': type_.length})
1556
1533
        else:
1557
1534
            raise exc.InvalidRequestError("NVARCHAR requires a length when rendered on MySQL")
1558
 
    
 
1535
 
1559
1536
    def visit_NCHAR(self, type_):
1560
1537
        # We'll actually generate the equiv. "NATIONAL CHAR" instead of "NCHAR".
1561
1538
        if type_.length:
1562
1539
            return self._extend_string(type_, {'national':True}, "CHAR(%(length)s)" % {'length': type_.length})
1563
1540
        else:
1564
1541
            return self._extend_string(type_, {'national':True}, "CHAR")
1565
 
    
 
1542
 
1566
1543
    def visit_VARBINARY(self, type_):
1567
1544
        return "VARBINARY(%d)" % type_.length
1568
 
    
 
1545
 
1569
1546
    def visit_large_binary(self, type_):
1570
1547
        return self.visit_BLOB(type_)
1571
 
    
 
1548
 
1572
1549
    def visit_enum(self, type_):
1573
1550
        if not type_.native_enum:
1574
1551
            return super(MySQLTypeCompiler, self).visit_enum(type_)
1575
1552
        else:
1576
1553
            return self.visit_ENUM(type_)
1577
 
    
 
1554
 
1578
1555
    def visit_BLOB(self, type_):
1579
1556
        if type_.length:
1580
1557
            return "BLOB(%d)" % type_.length
1581
1558
        else:
1582
1559
            return "BLOB"
1583
 
    
 
1560
 
1584
1561
    def visit_TINYBLOB(self, type_):
1585
1562
        return "TINYBLOB"
1586
1563
 
1595
1572
        for e in type_.enums:
1596
1573
            quoted_enums.append("'%s'" % e.replace("'", "''"))
1597
1574
        return self._extend_string(type_, {}, "ENUM(%s)" % ",".join(quoted_enums))
1598
 
        
 
1575
 
1599
1576
    def visit_SET(self, type_):
1600
1577
        return self._extend_string(type_, {}, "SET(%s)" % ",".join(type_._ddl_values))
1601
1578
 
1602
1579
    def visit_BOOLEAN(self, type):
1603
1580
        return "BOOL"
1604
 
        
 
1581
 
1605
1582
 
1606
1583
class MySQLIdentifierPreparer(compiler.IdentifierPreparer):
1607
1584
 
1611
1588
        if not server_ansiquotes:
1612
1589
            quote = "`"
1613
1590
        else:
1614
 
            quote = '"'    
 
1591
            quote = '"'
1615
1592
 
1616
1593
        super(MySQLIdentifierPreparer, self).__init__(
1617
1594
                                                dialect, 
1625
1602
 
1626
1603
class MySQLDialect(default.DefaultDialect):
1627
1604
    """Details of the MySQL dialect.  Not used directly in application code."""
1628
 
    
 
1605
 
1629
1606
    name = 'mysql'
1630
1607
    supports_alter = True
 
1608
 
1631
1609
    # identifiers are 64, however aliases can be 255...
1632
1610
    max_identifier_length = 255
1633
 
    
 
1611
    max_index_name_length = 64
 
1612
 
1634
1613
    supports_native_enum = True
1635
 
    
 
1614
 
1636
1615
    supports_sane_rowcount = True
1637
1616
    supports_sane_multi_rowcount = False
1638
 
    
 
1617
 
1639
1618
    default_paramstyle = 'format'
1640
1619
    colspecs = colspecs
1641
 
    
 
1620
 
1642
1621
    statement_compiler = MySQLCompiler
1643
1622
    ddl_compiler = MySQLDDLCompiler
1644
1623
    type_compiler = MySQLTypeCompiler
1645
1624
    ischema_names = ischema_names
1646
1625
    preparer = MySQLIdentifierPreparer
1647
 
    
 
1626
 
1648
1627
    # default SQL compilation settings -
1649
1628
    # these are modified upon initialize(), 
1650
1629
    # i.e. first connect
1651
1630
    _backslash_escapes = True
1652
1631
    _server_ansiquotes = False
1653
 
    
 
1632
 
1654
1633
    def __init__(self, use_ansiquotes=None, **kwargs):
1655
1634
        default.DefaultDialect.__init__(self, **kwargs)
1656
1635
 
1710
1689
        if isinstance(e, self.dbapi.OperationalError):
1711
1690
            return self._extract_error_code(e) in \
1712
1691
                        (2006, 2013, 2014, 2045, 2055)
1713
 
        elif isinstance(e, self.dbapi.InterfaceError):  
 
1692
        elif isinstance(e, self.dbapi.InterfaceError):
1714
1693
            # if underlying connection is closed, 
1715
1694
            # this is the error you get
1716
1695
            return "(0, '')" in str(e)
1734
1713
 
1735
1714
    def _extract_error_code(self, exception):
1736
1715
        raise NotImplementedError()
1737
 
    
 
1716
 
1738
1717
    def _get_default_schema_name(self, connection):
1739
1718
        return connection.execute('SELECT DATABASE()').scalar()
1740
1719
 
1769
1748
        finally:
1770
1749
            if rs:
1771
1750
                rs.close()
1772
 
    
 
1751
 
1773
1752
    def initialize(self, connection):
1774
1753
        default.DefaultDialect.initialize(self, connection)
1775
1754
        self._connection_charset = self._detect_charset(connection)
1786
1765
    def _supports_cast(self):
1787
1766
        return self.server_version_info is None or \
1788
1767
                    self.server_version_info >= (4, 0, 2)
1789
 
        
 
1768
 
1790
1769
    @reflection.cache
1791
1770
    def get_schema_names(self, connection, **kw):
1792
1771
        rp = connection.execute("SHOW schemas")
1811
1790
 
1812
1791
            return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
1813
1792
                                                        if row[1] == 'BASE TABLE']
1814
 
            
 
1793
 
1815
1794
    @reflection.cache
1816
1795
    def get_view_names(self, connection, schema=None, **kw):
1817
1796
        charset = self._connection_charset
1853
1832
 
1854
1833
        parsed_state = self._parsed_state_or_create(connection, table_name, schema, **kw)
1855
1834
        default_schema = None
1856
 
        
 
1835
 
1857
1836
        fkeys = []
1858
1837
 
1859
1838
        for spec in parsed_state.constraints:
1891
1870
    def get_indexes(self, connection, table_name, schema=None, **kw):
1892
1871
 
1893
1872
        parsed_state = self._parsed_state_or_create(connection, table_name, schema, **kw)
1894
 
        
 
1873
 
1895
1874
        indexes = []
1896
1875
        for spec in parsed_state.keys:
1897
1876
            unique = False
1931
1910
                        schema, 
1932
1911
                        info_cache=kw.get('info_cache', None)
1933
1912
                    )
1934
 
    
 
1913
 
1935
1914
    @util.memoized_property
1936
1915
    def _tabledef_parser(self):
1937
1916
        """return the MySQLTableDefinitionParser, generate if needed.
1938
 
        
 
1917
 
1939
1918
        The deferred creation ensures that the dialect has 
1940
1919
        retrieved server version information first.
1941
 
        
 
1920
 
1942
1921
        """
1943
1922
        if (self.server_version_info < (4, 1) and self._server_ansiquotes):
1944
1923
            # ANSI_QUOTES doesn't affect SHOW CREATE TABLE on < 4.1
1946
1925
        else:
1947
1926
            preparer = self.identifier_preparer
1948
1927
        return MySQLTableDefinitionParser(self, preparer)
1949
 
        
 
1928
 
1950
1929
    @reflection.cache
1951
1930
    def _setup_parser(self, connection, table_name, schema=None, **kw):
1952
1931
        charset = self._connection_charset
1961
1940
                                           full_name=full_name)
1962
1941
            sql = parser._describe_to_create(table_name, columns)
1963
1942
        return parser.parse(sql, charset)
1964
 
  
 
1943
 
1965
1944
    def _adjust_casing(self, table, charset=None):
1966
1945
        """Adjust Table name to the server case sensitivity, if needed."""
1967
1946
 
2035
2014
                mode = (mode_no | 4 == mode_no) and 'ANSI_QUOTES' or ''
2036
2015
 
2037
2016
        self._server_ansiquotes = 'ANSI_QUOTES' in mode
2038
 
        
 
2017
 
2039
2018
        # as of MySQL 5.0.1
2040
2019
        self._backslash_escapes = 'NO_BACKSLASH_ESCAPES' not in mode
2041
 
        
 
2020
 
2042
2021
    def _show_create_table(self, connection, table, charset=None,
2043
2022
                           full_name=None):
2044
2023
        """Run SHOW CREATE TABLE for a ``Table``."""
2087
2066
 
2088
2067
class ReflectedState(object):
2089
2068
    """Stores raw information about a SHOW CREATE TABLE statement."""
2090
 
    
 
2069
 
2091
2070
    def __init__(self):
2092
2071
        self.columns = []
2093
2072
        self.table_options = {}
2094
2073
        self.table_name = None
2095
2074
        self.keys = []
2096
2075
        self.constraints = []
2097
 
        
 
2076
 
2098
2077
class MySQLTableDefinitionParser(object):
2099
2078
    """Parses the results of a SHOW CREATE TABLE statement."""
2100
 
    
 
2079
 
2101
2080
    def __init__(self, dialect, preparer):
2102
2081
        self.dialect = dialect
2103
2082
        self.preparer = preparer
2130
2109
                    state.constraints.append(spec)
2131
2110
                else:
2132
2111
                    pass
2133
 
                    
 
2112
 
2134
2113
        return state
2135
 
        
 
2114
 
2136
2115
    def _parse_constraints(self, line):
2137
2116
        """Parse a KEY or CONSTRAINT line.
2138
2117
 
2283
2262
        if default == 'NULL':
2284
2263
            # eliminates the need to deal with this later.
2285
2264
            default = None
2286
 
            
 
2265
 
2287
2266
        col_d = dict(name=name, type=type_instance, default=default)
2288
2267
        col_d.update(col_kw)
2289
2268
        state.columns.append(col_d)
2390
2369
            r'(?: +COLLATE +(?P<collate>[\w_]+))?'
2391
2370
            r'(?: +(?P<notnull>NOT NULL))?'
2392
2371
            r'(?: +DEFAULT +(?P<default>'
2393
 
              r'(?:NULL|\x27(?:\x27\x27|[^\x27])*\x27|\w+)'
2394
 
              r'(?:ON UPDATE \w+)?'
 
2372
              r'(?:NULL|\x27(?:\x27\x27|[^\x27])*\x27|\w+'
 
2373
              r'(?: +ON UPDATE \w+)?)'
2395
2374
            r'))?'
2396
2375
            r'(?: +(?P<autoincr>AUTO_INCREMENT))?'
2397
2376
            r'(?: +COMMENT +(P<comment>(?:\x27\x27|[^\x27])+))?'