diff options
author | Jason Kirtland <jek@discorporate.us> | 2007-08-28 23:44:00 +0000 |
---|---|---|
committer | Jason Kirtland <jek@discorporate.us> | 2007-08-28 23:44:00 +0000 |
commit | 731dd1e96f7f428efca8cd5df46af092ead7a7e0 (patch) | |
tree | 5d9fba1f5e1a0a7f2f02ad96a90af0443a249c2e /lib/sqlalchemy/databases/mysql.py | |
parent | b11fde9cff282de6ab6940782af593da2bda7791 (diff) | |
download | sqlalchemy-731dd1e96f7f428efca8cd5df46af092ead7a7e0.tar.gz |
- Restored reflection for mysql VIEWs [ticket:748]
- Fixed anonymous pk reflection for mysql 5.1
- Tested table and view reflection against the 'sakila' database from
MySQL AB on 3.23 - 6.0. (with some schema adjustments, obviously)
Maybe this will go into the SA test suite someday.
- Tweaked mysql server version tuplification, now also splitting on hyphens
- Light janitorial
Diffstat (limited to 'lib/sqlalchemy/databases/mysql.py')
-rw-r--r-- | lib/sqlalchemy/databases/mysql.py | 142 |
1 files changed, 114 insertions, 28 deletions
diff --git a/lib/sqlalchemy/databases/mysql.py b/lib/sqlalchemy/databases/mysql.py index 298823329..af01cf84e 100644 --- a/lib/sqlalchemy/databases/mysql.py +++ b/lib/sqlalchemy/databases/mysql.py @@ -1543,7 +1543,8 @@ class MySQLDialect(default.DefaultDialect): """Convert a MySQL-python server_info string into a tuple.""" version = [] - for n in dbapi_con.get_server_info().split('.'): + r = re.compile('[.\-]') + for n in r.split(dbapi_con.get_server_info()): try: version.append(int(n)) except ValueError: @@ -1567,14 +1568,6 @@ class MySQLDialect(default.DefaultDialect): """Load column definitions from the server.""" charset = self._detect_charset(connection) - casing = self._detect_casing(connection, charset) - # is this really needed? - if casing == 1 and table.name != table.name.lower(): - table.name = table.name.lower() - lc_alias = schema._get_table_key(table.name, table.schema) - table.metadata.tables[lc_alias] = table - - sql = self._show_create_table(connection, table, charset) try: reflector = self.reflector @@ -1582,7 +1575,31 @@ class MySQLDialect(default.DefaultDialect): self.reflector = reflector = \ MySQLSchemaReflector(self.identifier_preparer) - reflector.reflect(connection, table, sql, charset, only=include_columns) + sql = self._show_create_table(connection, table, charset) + if sql.startswith('CREATE ALGORITHM'): + # Adapt views to something table-like. + columns = self._describe_table(connection, table, charset) + sql = reflector._describe_to_create(table, columns) + + self._adjust_casing(connection, table, charset) + + return reflector.reflect(connection, table, sql, charset, + only=include_columns) + + def _adjust_casing(self, connection, table, charset=None): + """Adjust Table name to the server case sensitivity, if needed.""" + + if charset is None: + charset = self._detect_charset(connection) + + casing = self._detect_casing(connection, charset) + + # For winxx database hosts. TODO: is this really needed? + if casing == 1 and table.name != table.name.lower(): + table.name = table.name.lower() + lc_alias = schema._get_table_key(table.name, table.schema) + table.metadata.tables[lc_alias] = table + def _detect_charset(self, connection): """Sniff out the character set in use for connection results.""" @@ -1701,9 +1718,36 @@ class MySQLDialect(default.DefaultDialect): return sql + def _describe_table(self, connection, table, charset=None, + full_name=None): + """Run DESCRIBE for a ``Table`` and return processed rows.""" + + if full_name is None: + full_name = self.identifier_preparer.format_table(table) + st = "DESCRIBE %s" % full_name + + rp, rows = None, None + try: + try: + rp = connection.execute(st) + except exceptions.SQLError, e: + if e.orig.args[0] == 1146: + raise exceptions.NoSuchTableError(full_name) + else: + raise + rows = _compat_fetchall(rp, charset=charset) + finally: + if rp: + rp.close() + return rows class _MySQLPythonRowProxy(object): - """Return consistent column values for all versions of MySQL-python (esp. alphas) and Unicode settings.""" + """Return consistent column values for all versions of MySQL-python. + + Smooth over data type issues (esp. with alpha driver versions) and + normalize strings as Unicode regardless of user-configured driver + encoding settings. + """ # Some MySQL-python versions can return some columns as # sets.Set(['value']) (seriously) but thankfully that doesn't @@ -1732,13 +1776,10 @@ class _MySQLPythonRowProxy(object): class MySQLCompiler(compiler.DefaultCompiler): operators = compiler.DefaultCompiler.operators.copy() - operators.update( - { - sql_operators.concat_op: \ - lambda x, y: "concat(%s, %s)" % (x, y), - sql_operators.mod: '%%' - } - ) + operators.update({ + sql_operators.concat_op: lambda x, y: "concat(%s, %s)" % (x, y), + sql_operators.mod: '%%' + }) def visit_cast(self, cast, **kwargs): if isinstance(cast.type, (sqltypes.Date, sqltypes.Time, @@ -1890,7 +1931,7 @@ class MySQLSchemaReflector(object): elif not line: pass else: - type_, spec = self.constraints(line) + type_, spec = self.parse_constraints(line) if type_ is None: warnings.warn( RuntimeWarning("Unknown schema content: %s" % @@ -1917,10 +1958,10 @@ class MySQLSchemaReflector(object): # Don't override by default. if table.name is None: - table.name = self.name(line) + table.name = self.parse_name(line) def _add_column(self, table, line, charset, only=None): - spec = self.column(line) + spec = self.parse_column(line) if not spec: warnings.warn(RuntimeWarning( "Unknown column definition %s" % line)) @@ -2097,7 +2138,7 @@ class MySQLSchemaReflector(object): The final line of SHOW CREATE TABLE output. """ - options = self.table_options(line) + options = self.parse_table_options(line) for nope in ('auto_increment', 'data_directory', 'index_directory'): options.pop(nope, None) @@ -2184,8 +2225,8 @@ class MySQLSchemaReflector(object): # KEY_BLOCK_SIZE size | WITH PARSER name self._re_key = _re_compile( r' ' - r'(?:(?P<type>\S+) )?KEY +' - r'(?:%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?' + r'(?:(?P<type>\S+) )?KEY' + r'(?: +%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?' r'(?: +USING +(?P<using>\S+))?' r' +\((?P<columns>.+?)\)' r'(?: +KEY_BLOCK_SIZE +(?P<keyblock>\S+))?' @@ -2260,7 +2301,7 @@ class MySQLSchemaReflector(object): self._pr_options.append(_pr_compile(regex)) - def name(self, line): + def parse_name(self, line): """Extract the table name. line @@ -2273,7 +2314,7 @@ class MySQLSchemaReflector(object): return None return cleanup(m.group('name')) - def column(self, line): + def parse_column(self, line): """Extract column details. Falls back to a 'minimal support' variant if full parse fails. @@ -2294,7 +2335,7 @@ class MySQLSchemaReflector(object): return spec return None - def constraints(self, line): + def parse_constraints(self, line): """Parse a KEY or CONSTRAINT line. line @@ -2330,7 +2371,7 @@ class MySQLSchemaReflector(object): # No match. return (None, line) - def table_options(self, line): + def parse_table_options(self, line): """Build a dictionary of all reflected table-level options. line @@ -2356,6 +2397,51 @@ class MySQLSchemaReflector(object): return options + def _describe_to_create(self, table, columns): + """Re-format DESCRIBE output as a SHOW CREATE TABLE string. + + DESCRIBE is a much simpler reflection and is sufficient for + reflecting views for runtime use. This method formats DDL + for columns only- keys are omitted. + + `columns` is a sequence of DESCRIBE or SHOW COLUMNS 6-tuples. + SHOW FULL COLUMNS FROM rows must be rearranged for use with + this function. + """ + + buffer = [] + for row in columns: + (name, col_type, nullable, default, extra) = \ + [row[i] for i in (0, 1, 2, 4, 5)] + + line = [' '] + line.append(self.preparer.quote_identifier(name)) + line.append(col_type) + if not nullable: + line.append('NOT NULL') + if default: + if 'auto_increment' in default: + pass + elif (col_type.startswith('timestamp') and + default.startswith('C')): + line.append('DEFAULT') + line.append(default) + elif default == 'NULL': + line.append('DEFAULT') + line.append(default) + else: + line.append('DEFAULT') + line.append("'%s'" % default.replace("'", "''")) + if extra: + line.append(extra) + + buffer.append(' '.join(line)) + + return ''.join([('CREATE TABLE %s (\n' % + self.preparer.quote_identifier(table.name)), + ',\n'.join(buffer), + '\n) ']) + def _parse_keyexprs(self, identifiers): """Unpack '"col"(2),"col" ASC'-ish strings into components.""" |