diff options
-rw-r--r-- | CHANGES | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/databases/mysql.py | 77 | ||||
-rw-r--r-- | lib/sqlalchemy/util.py | 3 | ||||
-rw-r--r-- | test/dialect/mysql.py | 49 |
4 files changed, 102 insertions, 36 deletions
@@ -7,12 +7,17 @@ CHANGES ======== - 0.5beta3 includes all bugfixes listed under release "0.4.7". - + - orm - Added a new SessionExtension hook called after_attach(). This is called at the point of attachment for objects via add(), add_all(), delete(), and merge(). - + +- mysql + - Quoting of MSEnum values for use in CREATE TABLE is now + optional & will be quoted on demand as required. (Quoting was + always optional for use with existing tables.) [ticket:1110] + 0.5beta2 ======== - 0.5beta2 includes some of the bugfixes listed under diff --git a/lib/sqlalchemy/databases/mysql.py b/lib/sqlalchemy/databases/mysql.py index c79e865fb..4a4cfbd90 100644 --- a/lib/sqlalchemy/databases/mysql.py +++ b/lib/sqlalchemy/databases/mysql.py @@ -1128,16 +1128,14 @@ class MSEnum(MSString): Example: - Column('myenum', MSEnum("'foo'", "'bar'", "'baz'")) + Column('myenum', MSEnum("foo", "bar", "baz")) Arguments are: enums - The range of valid values for this ENUM. Values will be used - exactly as they appear when generating schemas. Strings must - be quoted, as in the example above. Single-quotes are suggested - for ANSI compatability and are required for portability to servers - with ANSI_QUOTES enabled. + The range of valid values for this ENUM. Values will be quoted + when generating the schema according to the quoting flag (see + below). strict Defaults to False: ensure that a given value is in this ENUM's @@ -1167,20 +1165,58 @@ class MSEnum(MSString): that matches the column's character set. Generates BINARY in schema. This does not affect the type of data stored, only the collation of character data. - """ - self.__ddl_values = enums + quoting + Defaults to 'auto': automatically determine enum value quoting. If + all enum values are surrounded by the same quoting character, then + use 'quoted' mode. Otherwise, use 'unquoted' mode. - strip_enums = [] - for a in enums: - if a[0:1] == '"' or a[0:1] == "'": - # strip enclosing quotes and unquote interior - a = a[1:-1].replace(a[0] * 2, a[0]) - strip_enums.append(a) + 'quoted': values in enums are already quoted, they will be used + directly when generating the schema. + + 'unquoted': values in enums are not quoted, they will be escaped and + surrounded by single quotes when generating the schema. + + Previous versions of this type always required manually quoted + values to be supplied; future versions will always quote the string + literals for you. This is a transitional option. + + """ + self.quoting = kw.pop('quoting', 'auto') + + if self.quoting == 'auto': + # What quoting character are we using? + q = None + for e in enums: + if len(e) == 0: + self.quoting = 'unquoted' + break + elif q is None: + q = e[0] + + if e[0] != q or e[-1] != q: + self.quoting = 'unquoted' + break + else: + self.quoting = 'quoted' + + if self.quoting == 'quoted': + util.warn_pending_deprecation( + 'Manually quoting ENUM value literals is deprecated. Supply ' + 'unquoted values and use the quoting= option in cases of ' + 'ambiguity.') + strip_enums = [] + for a in enums: + if a[0:1] == '"' or a[0:1] == "'": + # strip enclosing quotes and unquote interior + a = a[1:-1].replace(a[0] * 2, a[0]) + strip_enums.append(a) + self.enums = strip_enums + else: + self.enums = list(enums) - self.enums = strip_enums self.strict = kw.pop('strict', False) - length = max([len(v) for v in strip_enums] + [0]) + length = max([len(v) for v in self.enums] + [0]) super(MSEnum, self).__init__(length, **kw) def bind_processor(self, dialect): @@ -1196,8 +1232,10 @@ class MSEnum(MSString): return process def get_col_spec(self): - return self._extend("ENUM(%s)" % ",".join(self.__ddl_values)) - + quoted_enums = [] + for e in self.enums: + quoted_enums.append("'%s'" % e.replace("'", "''")) + return self._extend("ENUM(%s)" % ",".join(quoted_enums)) class MSSet(MSString): """MySQL SET type.""" @@ -2180,6 +2218,9 @@ class MySQLSchemaReflector(object): if spec.get(kw, False): type_kw[kw] = spec[kw] + if type_ == 'enum': + type_kw['quoting'] = 'quoted' + type_instance = col_type(*type_args, **type_kw) col_args, col_kw = [], {} diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index d0027ebc4..a9e7d2238 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -1405,6 +1405,9 @@ def warn(msg): def warn_deprecated(msg): warnings.warn(msg, exc.SADeprecationWarning, stacklevel=3) +def warn_pending_deprecation(msg): + warnings.warn(msg, exc.SAPendingDeprecationWarning, stacklevel=3) + def deprecated(message=None, add_deprecation_to_docstring=True): """Decorates a function and issues a deprecation warning on use. diff --git a/test/dialect/mysql.py b/test/dialect/mysql.py index b796a56eb..415f5a653 100644 --- a/test/dialect/mysql.py +++ b/test/dialect/mysql.py @@ -3,6 +3,7 @@ import sets from sqlalchemy import * from sqlalchemy import sql, exc from sqlalchemy.databases import mysql +from testlib.testing import eq_ from testlib import * @@ -24,6 +25,7 @@ class TypesTest(TestBase, AssertsExecutionResults): Column('num4', mysql.MSDouble), Column('num5', mysql.MSDouble()), Column('enum1', mysql.MSEnum("'black'", "'white'")), + Column('enum2', mysql.MSEnum("dog", "cat")), ) try: table.drop(checkfirst=True) @@ -39,6 +41,7 @@ class TypesTest(TestBase, AssertsExecutionResults): assert isinstance(t2.c.num4.type, mysql.MSDouble) assert isinstance(t2.c.num5.type, mysql.MSDouble) assert isinstance(t2.c.enum1.type, mysql.MSEnum) + assert isinstance(t2.c.enum2.type, mysql.MSEnum) t2.drop() t2.create() finally: @@ -236,7 +239,7 @@ class TypesTest(TestBase, AssertsExecutionResults): (mysql.MSLongText, [], {'ascii':True}, 'LONGTEXT ASCII'), - (mysql.MSEnum, ["'foo'", "'bar'"], {'unicode':True}, + (mysql.MSEnum, ["foo", "bar"], {'unicode':True}, '''ENUM('foo','bar') UNICODE''') ] @@ -521,7 +524,10 @@ class TypesTest(TestBase, AssertsExecutionResults): nullable=False), Column('e3', mysql.MSEnum("'a'", "'b'", strict=True)), Column('e4', mysql.MSEnum("'a'", "'b'", strict=True), - nullable=False)) + nullable=False), + Column('e5', mysql.MSEnum("a", "b")), + Column('e6', mysql.MSEnum("'a'", "b")), + ) self.assert_eq(colspec(enum_table.c.e1), "e1 ENUM('a','b')") @@ -531,6 +537,10 @@ class TypesTest(TestBase, AssertsExecutionResults): "e3 ENUM('a','b')") self.assert_eq(colspec(enum_table.c.e4), "e4 ENUM('a','b') NOT NULL") + self.assert_eq(colspec(enum_table.c.e5), + "e5 ENUM('a','b')") + self.assert_eq(colspec(enum_table.c.e6), + "e6 ENUM('''a''','b')") enum_table.drop(checkfirst=True) enum_table.create() @@ -541,20 +551,23 @@ class TypesTest(TestBase, AssertsExecutionResults): self.assert_(True) try: - enum_table.insert().execute(e1='c', e2='c', e3='c', e4='c') + enum_table.insert().execute(e1='c', e2='c', e3='c', + e4='c', e5='c', e6='c') self.assert_(False) except exc.InvalidRequestError: self.assert_(True) enum_table.insert().execute() - enum_table.insert().execute(e1='a', e2='a', e3='a', e4='a') - enum_table.insert().execute(e1='b', e2='b', e3='b', e4='b') + enum_table.insert().execute(e1='a', e2='a', e3='a', + e4='a', e5='a', e6="'a'") + enum_table.insert().execute(e1='b', e2='b', e3='b', + e4='b', e5='b', e6='b') res = enum_table.select().execute().fetchall() - expected = [(None, 'a', None, 'a'), - ('a', 'a', 'a', 'a'), - ('b', 'b', 'b', 'b')] + expected = [(None, 'a', None, 'a', None, None), + ('a', 'a', 'a', 'a', 'a', "'a'"), + ('b', 'b', 'b', 'b', 'b', 'b')] # This is known to fail with MySQLDB 1.2.2 beta versions # which return these as sets.Set(['a']), sets.Set(['b']) @@ -588,9 +601,11 @@ class TypesTest(TestBase, AssertsExecutionResults): enum_table = Table('mysql_enum', MetaData(testing.db), Column('e1', mysql.MSEnum("'a'")), Column('e2', mysql.MSEnum("''")), - Column('e3', mysql.MSEnum("'a'", "''")), - Column('e4', mysql.MSEnum("''", "'a'")), - Column('e5', mysql.MSEnum("''", "'''a'''", "'b''b'", "''''"))) + Column('e3', mysql.MSEnum('a')), + Column('e4', mysql.MSEnum('')), + Column('e5', mysql.MSEnum("'a'", "''")), + Column('e6', mysql.MSEnum("''", "'a'")), + Column('e7', mysql.MSEnum("''", "'''a'''", "'b''b'", "''''"))) for col in enum_table.c: self.assert_(repr(col)) @@ -599,11 +614,13 @@ class TypesTest(TestBase, AssertsExecutionResults): reflected = Table('mysql_enum', MetaData(testing.db), autoload=True) for t in enum_table, reflected: - assert t.c.e1.type.enums == ["a"] - assert t.c.e2.type.enums == [""] - assert t.c.e3.type.enums == ["a", ""] - assert t.c.e4.type.enums == ["", "a"] - assert t.c.e5.type.enums == ["", "'a'", "b'b", "'"] + eq_(t.c.e1.type.enums, ["a"]) + eq_(t.c.e2.type.enums, [""]) + eq_(t.c.e3.type.enums, ["a"]) + eq_(t.c.e4.type.enums, [""]) + eq_(t.c.e5.type.enums, ["a", ""]) + eq_(t.c.e6.type.enums, ["", "a"]) + eq_(t.c.e7.type.enums, ["", "'a'", "b'b", "'"]) finally: enum_table.drop() |