]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- mysql.MSEnum value literals now automatically quoted when used in a CREATE.
authorJason Kirtland <jek@discorporate.us>
Wed, 16 Jul 2008 18:24:20 +0000 (18:24 +0000)
committerJason Kirtland <jek@discorporate.us>
Wed, 16 Jul 2008 18:24:20 +0000 (18:24 +0000)
  The change is backward compatible. Slight expansion of patch from catlee.
  Thanks! [ticket:1110]

CHANGES
lib/sqlalchemy/databases/mysql.py
lib/sqlalchemy/util.py
test/dialect/mysql.py

diff --git a/CHANGES b/CHANGES
index d35f2243a750fa04e1b32d5e441114e6ba162c42..60a1862a43dbc27a5579991f1bf2e8126fc53424 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -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 
index c79e865fbc9d69c949760be1b7fba186ce2c9296..4a4cfbd9013a05c401523fb900c57131cf0ec738 100644 (file)
@@ -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 = [], {}
index d0027ebc4759748cc3fbc2470243a44eeb91eb40..a9e7d22380f0878556f1c8831f85a6b77a52e1a5 100644 (file)
@@ -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.
 
index b796a56ebf404cea7384c6913c2ce18bb193411c..415f5a653f715ac7a4d29059849518cc5ee9a0ae 100644 (file)
@@ -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()