native enum support will by generate VARCHAR + an inline CHECK
constraint to enforce the enum.
[ticket:1109] [ticket:1511]
+
+ - The Boolean type, when used on a backend that doesn't
+ have native boolean support, will generate a CHECK
+ constraint "col IN (0, 1)" along with the int/smallint-
+ based column type. This can be switched off if
+ desired with create_constraint=False.
+ Note that MySQL has no native boolean *or* CHECK constraint
+ support so this feature isn't available on that platform.
+ [ticket:1589]
- PickleType now uses == for comparison of values when
mutable=True, unless the "comparator" argument with a
])
-class _FBBoolean(sqltypes.Boolean):
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
-
- def bind_processor(self, dialect):
- def process(value):
- if value is True:
- return 1
- elif value is False:
- return 0
- elif value is None:
- return None
- else:
- return value and True or False
- return process
-
-
colspecs = {
- sqltypes.Boolean: _FBBoolean,
}
ischema_names = {
sequences_optional = False
supports_default_values = True
postfetch_lastrowid = False
-
+
+ supports_native_boolean = False
+
requires_name_normalize = True
supports_empty_insert = False
+
statement_compiler = FBCompiler
ddl_compiler = FBDDLCompiler
preparer = FBIdentifierPreparer
class BIT(sqltypes.TypeEngine):
__visit_name__ = 'BIT'
-class _MSBoolean(sqltypes.Boolean):
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
-
- def bind_processor(self, dialect):
- def process(value):
- if value is True:
- return 1
- elif value is False:
- return 0
- elif value is None:
- return None
- else:
- return value and True or False
- return process
class MONEY(sqltypes.TypeEngine):
__visit_name__ = 'MONEY'
MSNumeric = _MSNumeric
MSDateTime = _MSDateTime
MSDate = _MSDate
-MSBoolean = _MSBoolean
MSReal = REAL
MSTinyInteger = TINYINT
MSTime = TIME
sqltypes.DateTime : _MSDateTime,
sqltypes.Date : _MSDate,
sqltypes.Time : TIME,
- sqltypes.Boolean : _MSBoolean,
}
ischema_names = {
colspecs = colspecs
ischema_names = ischema_names
+ supports_native_boolean = False
supports_unicode_binds = True
postfetch_lastrowid = True
update(..., mysql_limit=10)
+Boolean Types
+-------------
+
+MySQL's BOOL type is a synonym for SMALLINT, so is actually a numeric value,
+and additionally MySQL doesn't support CHECK constraints. Therefore SQLA's
+Boolean type cannot fully constrain values to just "True" and "False" the way it does for most other backends.
+
Troubleshooting
---------------
return value
return process
-class _MSBoolean(sqltypes.Boolean):
- """MySQL BOOLEAN type."""
-
- __visit_name__ = 'BOOLEAN'
-
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
-
- def bind_processor(self, dialect):
- def process(value):
- if value is True:
- return 1
- elif value is False:
- return 0
- elif value is None:
- return None
- else:
- return value and True or False
- return process
-
# old names
-MSBoolean = _MSBoolean
MSTime = _MSTime
MSSet = SET
MSEnum = ENUM
sqltypes.Numeric: NUMERIC,
sqltypes.Float: FLOAT,
sqltypes.Binary: _BinaryType,
- sqltypes.Boolean: _MSBoolean,
sqltypes.Time: _MSTime,
sqltypes.Enum: ENUM,
}
max_identifier_length = 255
supports_native_enum = True
- supports_native_boolean = True
supports_sane_rowcount = True
supports_sane_multi_rowcount = False
class _OracleBoolean(sqltypes.Boolean):
def get_dbapi_type(self, dbapi):
return dbapi.NUMBER
-
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
-
- def bind_processor(self, dialect):
- def process(value):
- if value is True:
- return 1
- elif value is False:
- return 0
- elif value is None:
- return None
- else:
- return value and True or False
- return process
colspecs = {
sqltypes.Boolean : _OracleBoolean,
supports_sane_rowcount = True
supports_native_enum = True
+ supports_native_boolean = True
supports_sequences = True
sequences_optional = True
def result_processor(self, dialect, coltype):
return self._result_processor(datetime.time)
-class _SLBoolean(sqltypes.Boolean):
- def bind_processor(self, dialect):
- def process(value):
- if value is None:
- return None
- return value and 1 or 0
- return process
-
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value == 1
- return process
-
colspecs = {
- sqltypes.Boolean: _SLBoolean,
sqltypes.Date: DATE,
sqltypes.DateTime: DATETIME,
sqltypes.Float: _SLFloat,
to generate primary key identifiers (i.e. Firebird, Postgresql,
Oracle).
- :param default: A scalar, Python callable, or :class:`~sqlalchemy.sql.expression.ClauseElement`
- representing the *default value* for this column, which will be
- invoked upon insert if this column is otherwise not specified
- in the VALUES clause of the insert. This is a shortcut
- to using :class:`ColumnDefault` as a positional argument.
+ :param default: A scalar, Python callable, or
+ :class:`~sqlalchemy.sql.expression.ClauseElement` representing the
+ *default value* for this column, which will be invoked upon insert
+ if this column is otherwise not specified in the VALUES clause of
+ the insert. This is a shortcut to using :class:`ColumnDefault` as
+ a positional argument.
- Contrast this argument to ``server_default`` which creates a
- default generator on the database side.
+ Contrast this argument to ``server_default`` which creates a
+ default generator on the database side.
- :param key: An optional string identifier which will identify this ``Column``
- object on the :class:`Table`. When a key is provided, this is the
- only identifier referencing the ``Column`` within the application,
- including ORM attribute mapping; the ``name`` field is used only
- when rendering SQL.
+ :param key: An optional string identifier which will identify this
+ ``Column`` object on the :class:`Table`. When a key is provided,
+ this is the only identifier referencing the ``Column`` within the
+ application, including ORM attribute mapping; the ``name`` field
+ is used only when rendering SQL.
:param index: When ``True``, indicates that the column is indexed.
- This is a shortcut for using a :class:`Index` construct on the table.
- To specify indexes with explicit names or indexes that contain multiple
- columns, use the :class:`Index` construct instead.
-
- :param info: A dictionary which defaults to ``{}``. A space to store application
- specific data. This must be a dictionary.
-
- :param nullable: If set to the default of ``True``, indicates the column
- will be rendered as allowing NULL, else it's rendered as NOT NULL.
- This parameter is only used when issuing CREATE TABLE statements.
-
- :param onupdate: A scalar, Python callable, or :class:`~sqlalchemy.sql.expression.ClauseElement`
- representing a default value to be applied to the column within UPDATE
- statements, which wil be invoked upon update if this column is not present
- in the SET clause of the update. This is a shortcut to using
- :class:`ColumnDefault` as a positional argument with ``for_update=True``.
+ This is a shortcut for using a :class:`Index` construct on the
+ table. To specify indexes with explicit names or indexes that
+ contain multiple columns, use the :class:`Index` construct
+ instead.
+
+ :param info: A dictionary which defaults to ``{}``. A space to store
+ application specific data. This must be a dictionary.
+
+ :param nullable: If set to the default of ``True``, indicates the
+ column will be rendered as allowing NULL, else it's rendered as
+ NOT NULL. This parameter is only used when issuing CREATE TABLE
+ statements.
+
+ :param onupdate: A scalar, Python callable, or
+ :class:`~sqlalchemy.sql.expression.ClauseElement` representing a
+ default value to be applied to the column within UPDATE
+ statements, which wil be invoked upon update if this column is not
+ present in the SET clause of the update. This is a shortcut to
+ using :class:`ColumnDefault` as a positional argument with
+ ``for_update=True``.
:param primary_key: If ``True``, marks this column as a primary key
- column. Multiple columns can have this flag set to specify composite
- primary keys. As an alternative, the primary key of a :class:`Table` can
- be specified via an explicit :class:`PrimaryKeyConstraint` object.
+ column. Multiple columns can have this flag set to specify
+ composite primary keys. As an alternative, the primary key of a
+ :class:`Table` can be specified via an explicit
+ :class:`PrimaryKeyConstraint` object.
- :param server_default: A :class:`FetchedValue` instance, str, Unicode or
- :func:`~sqlalchemy.sql.expression.text` construct representing
- the DDL DEFAULT value for the column.
+ :param server_default: A :class:`FetchedValue` instance, str, Unicode
+ or :func:`~sqlalchemy.sql.expression.text` construct representing
+ the DDL DEFAULT value for the column.
- String types will be emitted as-is, surrounded by single quotes::
+ String types will be emitted as-is, surrounded by single quotes::
- Column('x', Text, server_default="val")
+ Column('x', Text, server_default="val")
- x TEXT DEFAULT 'val'
+ x TEXT DEFAULT 'val'
- A :func:`~sqlalchemy.sql.expression.text` expression will be
- rendered as-is, without quotes::
+ A :func:`~sqlalchemy.sql.expression.text` expression will be
+ rendered as-is, without quotes::
- Column('y', DateTime, server_default=text('NOW()'))0
+ Column('y', DateTime, server_default=text('NOW()'))0
- y DATETIME DEFAULT NOW()
+ y DATETIME DEFAULT NOW()
- Strings and text() will be converted into a :class:`DefaultClause`
- object upon initialization.
+ Strings and text() will be converted into a :class:`DefaultClause`
+ object upon initialization.
- Use :class:`FetchedValue` to indicate that an already-existing column will generate
- a default value on the database side which will be available to SQLAlchemy
- for post-fetch after inserts.
- This construct does not specify any DDL and the implementation is
- left to the database, such as via a trigger.
-
- :param server_onupdate: A :class:`FetchedValue` instance representing
- a database-side default generation function. This indicates to
- SQLAlchemy that a newly generated value will be available after updates.
- This construct does not specify any DDL and the implementation is
- left to the database, such as via a trigger.
-
- :param quote: Force quoting of this column's name on or off, corresponding
- to ``True`` or ``False``. When left at its default of ``None``,
- the column identifier will be quoted according to whether the name is
- case sensitive (identifiers with at least one upper case character are
- treated as case sensitive), or if it's a reserved word. This flag
- is only needed to force quoting of a reserved word which is not known
- by the SQLAlchemy dialect.
-
- :param unique: When ``True``, indicates that this column contains a unique
- constraint, or if ``index`` is ``True`` as well, indicates that the
- :class:`Index` should be created with the unique flag. To specify multiple
- columns in the constraint/index or to specify an explicit name,
- use the :class:`UniqueConstraint` or :class:`Index` constructs explicitly.
+ Use :class:`FetchedValue` to indicate that an already-existing
+ column will generate a default value on the database side which
+ will be available to SQLAlchemy for post-fetch after inserts. This
+ construct does not specify any DDL and the implementation is left
+ to the database, such as via a trigger.
+
+ :param server_onupdate: A :class:`FetchedValue` instance
+ representing a database-side default generation function. This
+ indicates to SQLAlchemy that a newly generated value will be
+ available after updates. This construct does not specify any DDL
+ and the implementation is left to the database, such as via a
+ trigger.
+
+ :param quote: Force quoting of this column's name on or off,
+ corresponding to ``True`` or ``False``. When left at its default
+ of ``None``, the column identifier will be quoted according to
+ whether the name is case sensitive (identifiers with at least one
+ upper case character are treated as case sensitive), or if it's a
+ reserved word. This flag is only needed to force quoting of a
+ reserved word which is not known by the SQLAlchemy dialect.
+
+ :param unique: When ``True``, indicates that this column contains a
+ unique constraint, or if ``index`` is ``True`` as well, indicates
+ that the :class:`Index` should be created with the unique flag.
+ To specify multiple columns in the constraint/index or to specify
+ an explicit name, use the :class:`UniqueConstraint` or
+ :class:`Index` constructs explicitly.
"""
self.constraints = set()
self.foreign_keys = util.OrderedSet()
self._table_events = set()
-
- if isinstance(self.type, types.SchemaType):
+
+ # check if this Column is proxying another column
+ if '_proxies' in kwargs:
+ self.proxies = kwargs.pop('_proxies')
+ # otherwise, add DDL-related events
+ elif isinstance(self.type, types.SchemaType):
self.type._set_parent(self)
if self.default is not None:
args.append(self.default)
else:
args.append(ColumnDefault(self.default))
+
if self.server_default is not None:
if isinstance(self.server_default, FetchedValue):
args.append(self.server_default)
key = name or self.key,
primary_key = self.primary_key,
nullable = self.nullable,
- quote=self.quote, *fk)
+ quote=self.quote, _proxies=[self], *fk)
c.table = selectable
- c.proxies = [self]
selectable.columns.add(c)
if self.primary_key:
selectable.primary_key.add(c)
return False
def get_dbapi_type(self, dbapi):
- """Return the corresponding type object from the underlying DB-API, if any.
+ """Return the corresponding type object from the underlying DB-API, if
+ any.
+
+ This can be useful for calling ``setinputsizes()``, for example.
- This can be useful for calling ``setinputsizes()``, for example.
"""
return None
translate it to a new operator based on the semantics of this type.
By default, returns the operator unchanged.
+
"""
return op
return self.mutable
-class Boolean(TypeEngine):
+class Boolean(TypeEngine, SchemaType):
"""A bool datatype.
Boolean typically uses BOOLEAN or SMALLINT on the DDL side, and on
__visit_name__ = 'boolean'
+ def __init__(self, create_constraint=True, name=None):
+ """Construct a Boolean.
+
+ :param create_constraint: defaults to True. If the boolean
+ is generated as an int/smallint, also create a CHECK constraint
+ on the table that ensures 1 or 0 as a value.
+
+ :param name: if a CHECK constraint is generated, specify
+ the name of the constraint.
+
+ """
+ self.create_constraint = create_constraint
+ self.name = name
+
+ def _set_table(self, table, column):
+ if not self.create_constraint:
+ return
+
+ def should_create_constraint(compiler):
+ return not compiler.dialect.supports_native_boolean
+
+ e = schema.CheckConstraint(
+ column.in_([0, 1]),
+ name=self.name,
+ _create_rule=should_create_constraint
+ )
+ table.append_constraint(e)
+
+ def result_processor(self, dialect, coltype):
+ if dialect.supports_native_boolean:
+ return None
+ else:
+ def process(value):
+ if value is None:
+ return None
+ return value and True or False
+ return process
+
class Interval(TypeDecorator):
"""A type for ``datetime.timedelta()`` objects.
columns = [
# column type, args, kwargs, expected ddl
- (mssql.MSBoolean, [], {},
+ (Boolean, [], {},
'BIT'),
]
meta = MetaData(testing.db)
bool_table = Table('mysql_bool', meta,
Column('b1', BOOLEAN),
- Column('b2', mysql.MSBoolean),
+ Column('b2', Boolean),
Column('b3', mysql.MSTinyInteger(1)),
Column('b4', mysql.MSTinyInteger))
# testing
(Boolean, "t.col"),
(BOOLEAN, "t.col"),
- (m.MSBoolean, "t.col"),
(m.MSEnum, "t.col"),
(m.MSEnum("1", "2"), "t.col"),
meta = MetaData(testing.db)
t = Table('bool_table', meta,
Column('id', Integer, primary_key=True),
- Column('boo', Boolean))
+ Column('boo', Boolean(create_constraint=False)))
try:
meta.create_all()
testing.db.execute("INSERT INTO bool_table (id, boo) VALUES (4, '0');")
testing.db.execute("INSERT INTO bool_table (id, boo) VALUES (5, 1);")
testing.db.execute("INSERT INTO bool_table (id, boo) VALUES (6, 0);")
- assert t.select(t.c.boo).order_by(t.c.id).execute().fetchall() == [(3, True,), (5, True,)]
+ eq_(
+ t.select(t.c.boo).order_by(t.c.id).execute().fetchall(),
+ [(3, True,), (5, True,)]
+ )
finally:
meta.drop_all()
def test_string_dates_raise(self):
- assert_raises(TypeError, testing.db.execute, select([1]).where(bindparam("date", type_=Date)), date=str(datetime.date(2007, 10, 30)))
+ assert_raises(TypeError,
+ testing.db.execute,
+ select([1]).where(bindparam("date", type_=Date)),
+ date=str(datetime.date(2007, 10, 30)))
def test_time_microseconds(self):
dt = datetime.datetime(2008, 6, 27, 12, 0, 0, 125) # 125 usec
@classmethod
def define_tables(cls, metadata):
- # determine a literal value for "false" based on the dialect
- # FIXME: this DefaultClause setup is bogus.
-
- dialect = testing.db.dialect
- bp = sa.Boolean().dialect_impl(dialect).bind_processor(dialect)
-
- if bp:
- false = str(bp(False))
- elif testing.against('maxdb'):
- false = text('FALSE')
+
+ if testing.db.dialect.supports_native_boolean:
+ false = 'false'
else:
- false = str(False)
+ false = "0"
+
cls.other_artifacts['false'] = false
Table('owners', metadata ,
metadata = MetaData(testing.db)
bool_table = Table('booltest', metadata,
Column('id', Integer, primary_key=True),
- Column('value', Boolean))
+ Column('value', Boolean),
+ Column('unconstrained_value', Boolean(create_constraint=False)),
+ )
bool_table.create()
+
@classmethod
def teardown_class(cls):
bool_table.drop()
- def testbasic(self):
+
+ def teardown(self):
+ bool_table.delete().execute()
+
+ def test_boolean(self):
bool_table.insert().execute(id=1, value=True)
bool_table.insert().execute(id=2, value=False)
bool_table.insert().execute(id=3, value=True)
bool_table.insert().execute(id=4, value=True)
bool_table.insert().execute(id=5, value=True)
+ bool_table.insert().execute(id=6, value=None)
- res = bool_table.select(
+ res = select([bool_table.c.id, bool_table.c.value]).where(
bool_table.c.value == True
).order_by(bool_table.c.id).execute().fetchall()
eq_(res, [(1, True), (3, True), (4, True), (5, True)])
- res2 = bool_table.select(bool_table.c.value == False).execute().fetchall()
+ res2 = select([bool_table.c.id, bool_table.c.value]).where(
+ bool_table.c.value == False).execute().fetchall()
eq_(res2, [(2, False)])
+ res3 = select([bool_table.c.id, bool_table.c.value]).\
+ order_by(bool_table.c.id).\
+ execute().fetchall()
+ eq_(res3, [(1, True), (2, False),
+ (3, True), (4, True),
+ (5, True), (6, None)])
+
+ # ensure we're getting True/False, not just ints
+ assert res3[0][1] is True
+ assert res3[1][1] is False
+
+ @testing.fails_on('mysql',
+ "The CHECK clause is parsed but ignored by all storage engines.")
+ @testing.skip_if(lambda: testing.db.dialect.supports_native_boolean)
+ def test_constraint(self):
+ assert_raises((exc.IntegrityError, exc.ProgrammingError),
+ testing.db.execute,
+ "insert into booltest (id, value) values(1, 5)")
+
+ @testing.skip_if(lambda: testing.db.dialect.supports_native_boolean)
+ def test_unconstrained(self):
+ testing.db.execute(
+ "insert into booltest (id, unconstrained_value) values (1, 5)")
+
+
class PickleTest(TestBase):
def test_eq_comparison(self):
p1 = PickleType()