summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES9
-rw-r--r--lib/sqlalchemy/databases/mysql.py77
-rw-r--r--lib/sqlalchemy/util.py3
-rw-r--r--test/dialect/mysql.py49
4 files changed, 102 insertions, 36 deletions
diff --git a/CHANGES b/CHANGES
index d35f2243a..60a1862a4 100644
--- 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
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()