diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/dialects/oracle/cx_oracle.py | 123 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/base.py | 69 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/pg8000.py | 22 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/psycopg2.py | 15 | ||||
-rwxr-xr-x | lib/sqlalchemy/ext/declarative.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 63 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/deprecated_interfaces.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/events.py | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 7 | ||||
-rw-r--r-- | lib/sqlalchemy/processors.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/types.py | 6 |
14 files changed, 277 insertions, 63 deletions
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index eb25e614e..87a84e514 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -66,6 +66,52 @@ Two Phase Transaction Support Two Phase transactions are implemented using XA transactions. Success has been reported with this feature but it should be regarded as experimental. +Precision Numerics +------------------ + +The SQLAlchemy dialect goes thorugh a lot of steps to ensure +that decimal numbers are sent and received with full accuracy. +An "outputtypehandler" callable is associated with each +cx_oracle connection object which detects numeric types and +receives them as string values, instead of receiving a Python +``float`` directly, which is then passed to the Python +``Decimal`` constructor. The :class:`.Numeric` and +:class:`.Float` types under the cx_oracle dialect are aware of +this behavior, and will coerce the ``Decimal`` to ``float`` if +the ``asdecimal`` flag is ``False`` (default on :class:`.Float`, +optional on :class:`.Numeric`). + +The handler attempts to use the "precision" and "scale" +attributes of the result set column to best determine if +subsequent incoming values should be received as ``Decimal`` as +opposed to int (in which case no processing is added). There are +several scenarios where OCI_ does not provide unambiguous data +as to the numeric type, including some situations where +individual rows may return a combination of floating point and +integer values. Certain values for "precision" and "scale" have +been observed to determine this scenario. When it occurs, the +outputtypehandler receives as string and then passes off to a +processing function which detects, for each returned value, if a +decimal point is present, and if so converts to ``Decimal``, +otherwise to int. The intention is that simple int-based +statements like "SELECT my_seq.nextval() FROM DUAL" continue to +return ints and not ``Decimal`` objects, and that any kind of +floating point value is received as a string so that there is no +floating point loss of precision. + +The "decimal point is present" logic itself is also sensitive to +locale. Under OCI_, this is controlled by the NLS_LANG +environment variable. Upon first connection, the dialect runs a +test to determine the current "decimal" character, which can be +a comma "," for european locales. From that point forward the +outputtypehandler uses that character to represent a decimal +point (this behavior is new in version 0.6.6). Note that +cx_oracle 5.0.3 or greater is required when dealing with +numerics with locale settings that don't use a period "." as the +decimal character. + +.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html + """ from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \ @@ -76,6 +122,7 @@ from sqlalchemy import types as sqltypes, util, exc, processors from datetime import datetime import random from decimal import Decimal +import re class _OracleNumeric(sqltypes.Numeric): def bind_processor(self, dialect): @@ -473,37 +520,80 @@ class OracleDialect_cx_oracle(OracleDialect): self.dbapi.BLOB: oracle.BLOB(), self.dbapi.BINARY: oracle.RAW(), } + @classmethod + def dbapi(cls): + import cx_Oracle + return cx_Oracle def initialize(self, connection): super(OracleDialect_cx_oracle, self).initialize(connection) if self._is_oracle_8: self.supports_unicode_binds = False + self._detect_decimal_char(connection) + + def _detect_decimal_char(self, connection): + """detect if the decimal separator character is not '.', as + is the case with european locale settings for NLS_LANG. + + cx_oracle itself uses similar logic when it formats Python + Decimal objects to strings on the bind side (as of 5.0.3), + as Oracle sends/receives string numerics only in the + current locale. + + """ + if self.cx_oracle_ver < (5,): + # no output type handlers before version 5 + return + + cx_Oracle = self.dbapi + conn = connection.connection + + # override the output_type_handler that's + # on the cx_oracle connection with a plain + # one on the cursor + + def output_type_handler(cursor, name, defaultType, + size, precision, scale): + return cursor.var( + cx_Oracle.STRING, + 255, arraysize=cursor.arraysize) + + cursor = conn.cursor() + cursor.outputtypehandler = output_type_handler + cursor.execute("SELECT 0.1 FROM DUAL") + val = cursor.fetchone()[0] + cursor.close() + char = re.match(r"([\.,])", val).group(1) + if char != '.': + _detect_decimal = self._detect_decimal + self._detect_decimal = \ + lambda value: _detect_decimal(value.replace(char, '.')) + self._to_decimal = \ + lambda value: Decimal(value.replace(char, '.')) + + def _detect_decimal(self, value): + if "." in value: + return Decimal(value) + else: + return int(value) + + _to_decimal = Decimal - @classmethod - def dbapi(cls): - import cx_Oracle - return cx_Oracle - def on_connect(self): if self.cx_oracle_ver < (5,): # no output type handlers before version 5 return - def maybe_decimal(value): - if "." in value: - return Decimal(value) - else: - return int(value) - cx_Oracle = self.dbapi - def output_type_handler(cursor, name, defaultType, size, precision, scale): + def output_type_handler(cursor, name, defaultType, + size, precision, scale): # convert all NUMBER with precision + positive scale to Decimal # this almost allows "native decimal" mode. if defaultType == cx_Oracle.NUMBER and precision and scale > 0: return cursor.var( cx_Oracle.STRING, 255, - outconverter=Decimal, + outconverter=self._to_decimal, arraysize=cursor.arraysize) # if NUMBER with zero precision and 0 or neg scale, this appears # to indicate "ambiguous". Use a slower converter that will @@ -515,7 +605,7 @@ class OracleDialect_cx_oracle(OracleDialect): return cursor.var( cx_Oracle.STRING, 255, - outconverter=maybe_decimal, + outconverter=self._detect_decimal, arraysize=cursor.arraysize) # allow all strings to come back natively as Unicode elif defaultType in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR): @@ -578,7 +668,10 @@ class OracleDialect_cx_oracle(OracleDialect): return ([], opts) def _get_server_version_info(self, connection): - return tuple(int(x) for x in connection.connection.version.split('.')) + return tuple( + int(x) + for x in connection.connection.version.split('.') + ) def is_disconnect(self, e): if isinstance(e, self.dbapi.InterfaceError): diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 0d103cb0d..7b1a97c32 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -94,13 +94,18 @@ from sqlalchemy.sql import compiler, expression, util as sql_util from sqlalchemy.sql import operators as sql_operators from sqlalchemy import types as sqltypes +try: + from uuid import UUID as _python_UUID +except ImportError: + _python_UUID = None + from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, VARCHAR, \ CHAR, TEXT, FLOAT, NUMERIC, \ DATE, BOOLEAN -_DECIMAL_TYPES = (1700, 1231) +_DECIMAL_TYPES = (1231, 1700) _FLOAT_TYPES = (700, 701, 1021, 1022) - +_INT_TYPES = (20, 21, 23, 26, 1005, 1007, 1016) class REAL(sqltypes.Float): __visit_name__ = "REAL" @@ -134,6 +139,12 @@ class TIME(sqltypes.TIME): self.precision = precision class INTERVAL(sqltypes.TypeEngine): + """Postgresql INTERVAL type. + + The INTERVAL type may not be supported on all DBAPIs. + It is known to work on psycopg2 and not pg8000 or zxjdbc. + + """ __visit_name__ = 'INTERVAL' def __init__(self, precision=None): self.precision = precision @@ -156,17 +167,67 @@ class BIT(sqltypes.TypeEngine): PGBit = BIT class UUID(sqltypes.TypeEngine): + """Postgresql UUID type. + + Represents the UUID column type, interpreting + data either as natively returned by the DBAPI + or as Python uuid objects. + + The UUID type may not be supported on all DBAPIs. + It is known to work on psycopg2 and not pg8000. + + """ __visit_name__ = 'UUID' + + def __init__(self, as_uuid=False): + """Construct a UUID type. + + + :param as_uuid=False: if True, values will be interpreted + as Python uuid objects, converting to/from string via the + DBAPI. + + """ + if as_uuid and _python_UUID is None: + raise NotImplementedError( + "This version of Python does not support the native UUID type." + ) + self.as_uuid = as_uuid + + def bind_processor(self, dialect): + if self.as_uuid: + def process(value): + if value is not None: + value = str(value) + return value + return process + else: + return None + + def result_processor(self, dialect, coltype): + if self.as_uuid: + def process(value): + if value is not None: + value = _python_UUID(value) + return value + return process + else: + return None + PGUuid = UUID class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine): """Postgresql ARRAY type. Represents values as Python lists. + + The ARRAY type may not be supported on all DBAPIs. + It is known to work on psycopg2 and not pg8000. **Note:** be sure to read the notes for - :class:`~sqlalchemy.types.MutableType` regarding ORM - performance implications. + :class:`.MutableType` regarding ORM + performance implications. The :class:`.ARRAY` type's + mutability can be disabled using the "mutable" flag. """ __visit_name__ = 'ARRAY' diff --git a/lib/sqlalchemy/dialects/postgresql/pg8000.py b/lib/sqlalchemy/dialects/postgresql/pg8000.py index 6af2cbd76..7b1d8e6a7 100644 --- a/lib/sqlalchemy/dialects/postgresql/pg8000.py +++ b/lib/sqlalchemy/dialects/postgresql/pg8000.py @@ -9,14 +9,16 @@ URLs are of the form Unicode ------- -pg8000 requires that the postgresql client encoding be configured in the postgresql.conf file -in order to use encodings other than ascii. Set this value to the same value as -the "encoding" parameter on create_engine(), usually "utf-8". +pg8000 requires that the postgresql client encoding be +configured in the postgresql.conf file in order to use encodings +other than ascii. Set this value to the same value as the +"encoding" parameter on create_engine(), usually "utf-8". Interval -------- -Passing data from/to the Interval type is not supported as of yet. +Passing data from/to the Interval type is not supported as of +yet. """ import decimal @@ -27,26 +29,28 @@ from sqlalchemy import processors from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgresql.base import PGDialect, \ PGCompiler, PGIdentifierPreparer, PGExecutionContext,\ - _DECIMAL_TYPES, _FLOAT_TYPES + _DECIMAL_TYPES, _FLOAT_TYPES, _INT_TYPES class _PGNumeric(sqltypes.Numeric): def result_processor(self, dialect, coltype): if self.asdecimal: if coltype in _FLOAT_TYPES: return processors.to_decimal_processor_factory(decimal.Decimal) - elif coltype in _DECIMAL_TYPES: + elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: # pg8000 returns Decimal natively for 1700 return None else: - raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) + raise exc.InvalidRequestError( + "Unknown PG numeric type: %d" % coltype) else: if coltype in _FLOAT_TYPES: # pg8000 returns float natively for 701 return None - elif coltype in _DECIMAL_TYPES: + elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: return processors.to_float else: - raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) + raise exc.InvalidRequestError( + "Unknown PG numeric type: %d" % coltype) class PGExecutionContext_pg8000(PGExecutionContext): pass diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 04b4e1fb7..7c5562064 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -96,8 +96,9 @@ from sqlalchemy.sql import expression from sqlalchemy.sql import operators as sql_operators from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgresql.base import PGDialect, PGCompiler, \ - PGIdentifierPreparer, PGExecutionContext, \ - ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES + PGIdentifierPreparer, PGExecutionContext, \ + ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES,\ + _INT_TYPES logger = logging.getLogger('sqlalchemy.dialects.postgresql') @@ -111,19 +112,21 @@ class _PGNumeric(sqltypes.Numeric): if self.asdecimal: if coltype in _FLOAT_TYPES: return processors.to_decimal_processor_factory(decimal.Decimal) - elif coltype in _DECIMAL_TYPES: + elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: # pg8000 returns Decimal natively for 1700 return None else: - raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) + raise exc.InvalidRequestError( + "Unknown PG numeric type: %d" % coltype) else: if coltype in _FLOAT_TYPES: # pg8000 returns float natively for 701 return None - elif coltype in _DECIMAL_TYPES: + elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: return processors.to_float else: - raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) + raise exc.InvalidRequestError( + "Unknown PG numeric type: %d" % coltype) class _PGEnum(ENUM): def __init__(self, *arg, **kw): diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index dd2df63d3..3c6cab59a 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -999,6 +999,10 @@ def _as_declarative(cls, classname, dict_): isinstance(obj, declarative_props) ): table_args = cls.__table_args__ + if not isinstance(table_args, (tuple, dict, type(None))): + raise exceptions.ArgumentError( + "__table_args__ value must be a tuple, " + "dict, or None") if base is not cls: inherited_table_args = True elif class_mapped: diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 0f8a7d95c..f7145c433 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -204,6 +204,16 @@ def relationship(argument, secondary=None, **kwargs): generally mutually exclusive with the use of the *secondary* keyword argument. + :param active_history=False: + When ``True``, indicates that the "previous" value for a + many-to-one reference should be loaded when replaced, if + not already loaded. Normally, history tracking logic for + simple many-to-ones only needs to be aware of the "new" + value in order to perform a flush. This flag is available + for applications that make use of + :func:`.attributes.get_history` which also need to know + the "previous" value of the attribute. (New in 0.6.6) + :param backref: indicates the string name of a property to be placed on the related mapper's class that will handle this relationship in the other @@ -572,7 +582,7 @@ def column_property(*args, **kwargs): """Provide a column-level property for use with a Mapper. Column-based properties can normally be applied to the mapper's - ``properties`` dictionary using the ``schema.Column`` element directly. + ``properties`` dictionary using the :class:`.Column` element directly. Use this function when the given column is not directly present within the mapper's selectable; examples include SQL expressions, functions, and scalar SELECT queries. @@ -583,6 +593,16 @@ def column_property(*args, **kwargs): :param \*cols: list of Column objects to be mapped. + :param active_history=False: + When ``True``, indicates that the "previous" value for a + scalar attribute should be loaded when replaced, if not + already loaded. Normally, history tracking logic for + simple non-primary-key scalar values only needs to be + aware of the "new" value in order to perform a flush. This + flag is available for applications that make use of + :func:`.attributes.get_history` which also need to know + the "previous" value of the attribute. (new in 0.6.6) + :param comparator_factory: a class which extends :class:`.ColumnProperty.Comparator` which provides custom SQL clause generation for comparison operations. @@ -684,7 +704,7 @@ def mapper(class_, local_table=None, *args, **params): exist within the session, erasing any in-memory changes with whatever information was loaded from the database. Usage of this flag is highly discouraged; as an alternative, see the method - `populate_existing()` on :class:`~sqlalchemy.orm.query.Query`. + :meth:`.Query.populate_existing`. :param allow_null_pks: This flag is deprecated - this is stated as allow_partial_pks which defaults to True. @@ -693,7 +713,7 @@ def mapper(class_, local_table=None, *args, **params): composite primary key with some NULL values should be considered as possibly existing within the database. This affects whether a mapper will assign an incoming row to an existing identity, as well - as if session.merge() will check the database first for a + as if :meth:`.Session.merge` will check the database first for a particular primary key value. A "partial primary key" can occur if one has mapped to an OUTER JOIN, for example. @@ -702,13 +722,14 @@ def mapper(class_, local_table=None, *args, **params): that an instance will be fully saved before saving the next instance, which includes inserting/updating all table rows corresponding to the entity as well as calling all - :class:`MapperExtension` methods corresponding to the save + :class:`.MapperExtension` methods corresponding to the save operation. :param column_prefix: A string which will be prepended to the `key` - name of all Columns when creating column-based properties from the - given Table. Does not affect explicitly specified column-based - properties + name of all :class:`.Column` objects when creating + column-based properties from the + given :class:`.Table`. Does not affect explicitly specified + column-based properties :param concrete: If True, indicates this mapper should use concrete table inheritance with its parent mapper. @@ -735,13 +756,13 @@ def mapper(class_, local_table=None, *args, **params): present in the mapped table but not named or present in this list will not be automatically mapped. See also "exclude_properties". - :param inherits: Another :class:`~sqlalchemy.orm.Mapper` for which - this :class:`~sqlalchemy.orm.Mapper` will have an inheritance + :param inherits: Another :class:`.Mapper` for which + this :class:`.Mapper` will have an inheritance relationship with. :param inherit_condition: For joined table inheritance, a SQL expression (constructed - :class:`~sqlalchemy.expression.sql.ClauseElement`) which will + :class:`.ClauseElement`) which will define how the two tables are joined; defaults to a natural join between the two tables. @@ -753,7 +774,7 @@ def mapper(class_, local_table=None, *args, **params): the selection of instances, not their persistence. Any number of non_primary mappers may be created for a particular class. - :param order_by: A single :class:`Column` or list of :class:`Column` + :param order_by: A single :class:`.Column` or list of :class:`.Column` objects for which selection operations should use as the default ordering for entities. Defaults to the OID/ROWID of the table if any, or the first primary key column of the table. @@ -788,7 +809,7 @@ def mapper(class_, local_table=None, *args, **params): this flag. :param polymorphic_on: Used with mappers in an inheritance - relationship, a ``Column`` which will identify the class/mapper + relationship, a :class:`.Column` which will identify the class/mapper combination to be used with a particular row. Requires the ``polymorphic_identity`` value to be set for all mappers in the inheritance hierarchy. The column specified by ``polymorphic_on`` @@ -798,23 +819,23 @@ def mapper(class_, local_table=None, *args, **params): argument. :param polymorphic_identity: A value which will be stored in the - Column denoted by polymorphic_on, corresponding to the *class - identity* of this mapper. + Column denoted by polymorphic_on, corresponding to the class + identity of this mapper. :param properties: A dictionary mapping the string names of object attributes to ``MapperProperty`` instances, which define the persistence behavior of that attribute. Note that the columns in the mapped table are automatically converted into - ``ColumnProperty`` instances based on the `key` property of each - ``Column`` (although they can be overridden using this dictionary). + ``ColumnProperty`` instances based on the ``key`` property of each + :class:`.Column` (although they can be overridden using this dictionary). - :param primary_key: A list of ``Column`` objects which define the - *primary key* to be used against this mapper's selectable unit. - This is normally simply the primary key of the `local_table`, but + :param primary_key: A list of :class:`.Column` objects which define the + primary key to be used against this mapper's selectable unit. + This is normally simply the primary key of the ``local_table``, but can be overridden here. - :param version_id_col: A ``Column`` which must have an integer type - that will be used to keep a running *version id* of mapped entities + :param version_id_col: A :class:`.Column` which must have an integer type + that will be used to keep a running version id of mapped entities in the database. this is used during save operations to ensure that no other thread or process has updated the instance during the lifetime of the entity, else a :class:`StaleDataError` exception is diff --git a/lib/sqlalchemy/orm/deprecated_interfaces.py b/lib/sqlalchemy/orm/deprecated_interfaces.py index 3e4f268d0..52193b149 100644 --- a/lib/sqlalchemy/orm/deprecated_interfaces.py +++ b/lib/sqlalchemy/orm/deprecated_interfaces.py @@ -526,6 +526,10 @@ class AttributeExtension(object): active_history = True """indicates that the set() method would like to receive the 'old' value, even if it means firing lazy callables. + + Note that ``active_history`` can also be set directly via + :func:`.column_property` and :func:`.relationship`. + """ @classmethod diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index fc8cab2ed..b610408b7 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -783,9 +783,12 @@ class AttributeEvents(event.Events): Several modifiers are available to the :func:`~.event.listen` function. :param active_history=False: When True, indicates that the - "on_set" event would like to receive the "old" value - being replaced unconditionally, even if this requires - firing off database loads. + "on_set" event would like to receive the "old" value being + replaced unconditionally, even if this requires firing off + database loads. Note that ``active_history`` can also be + set directly via :func:`.column_property` and + :func:`.relationship`. + :param propagate=False: When True, the listener function will be established not just for the class attribute given, but for attributes of the same name on all current subclasses diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 75ba0b5c0..accb7c4be 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -104,7 +104,7 @@ class Mapper(object): self.class_manager = None - self.primary_key_argument = primary_key + self.primary_key_argument = util.to_list(primary_key) self.non_primary = non_primary if order_by is not False: diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index feee041ce..edfb861f4 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -60,6 +60,7 @@ class ColumnProperty(StrategizedProperty): self.__class__.Comparator) self.descriptor = kwargs.pop('descriptor', None) self.extension = kwargs.pop('extension', None) + self.active_history = kwargs.pop('active_history', False) if 'doc' in kwargs: self.doc = kwargs.pop('doc') @@ -114,6 +115,7 @@ class ColumnProperty(StrategizedProperty): return ColumnProperty( deferred=self.deferred, group=self.group, + active_history=self.active_history, *self.columns) def _getattr(self, state, dict_, column, passive=False): @@ -184,6 +186,7 @@ class CompositeProperty(ColumnProperty): deferred=self.deferred, group=self.group, composite_class=self.composite_class, + active_history=self.active_history, *self.columns) def do_init(self): @@ -444,6 +447,7 @@ class RelationshipProperty(StrategizedProperty): comparator_factory=None, single_parent=False, innerjoin=False, doc=None, + active_history=False, cascade_backrefs=True, load_on_pending=False, strategy_class=None, _local_remote_pairs=None, @@ -469,6 +473,7 @@ class RelationshipProperty(StrategizedProperty): self.query_class = query_class self.innerjoin = innerjoin self.doc = doc + self.active_history = active_history self.join_depth = join_depth self.local_remote_pairs = _local_remote_pairs self.extension = extension diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 5c608d1f4..a56b67546 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1523,7 +1523,10 @@ class Query(object): else: return list(res) else: - return list(self[item:item+1])[0] + if item == -1: + return list(self)[-1] + else: + return list(self[item:item+1])[0] @_generative(_no_statement_condition) def slice(self, start, stop): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 17f165b64..578ef2de1 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -109,7 +109,8 @@ class ColumnLoader(LoaderStrategy): self.is_class_level = True coltype = self.columns[0].type # TODO: check all columns ? check for foreign key as well? - active_history = self.columns[0].primary_key + active_history = self.parent_property.active_history or \ + self.columns[0].primary_key _register_attribute(self, mapper, useobject=False, compare_function=coltype.compare_values, @@ -164,8 +165,7 @@ class CompositeColumnLoader(ColumnLoader): _register_attribute(self, mapper, useobject=False, compare_function=compare, copy_function=copy, - mutable_scalars=True - #active_history ? + mutable_scalars=True, ) def create_row_processor(self, selectcontext, path, mapper, @@ -399,6 +399,7 @@ class LazyLoader(AbstractRelationshipLoader): uselist = self.parent_property.uselist, typecallable = self.parent_property.collection_class, active_history = \ + self.parent_property.active_history or \ self.parent_property.direction is not \ interfaces.MANYTOONE or \ not self.use_get, diff --git a/lib/sqlalchemy/processors.py b/lib/sqlalchemy/processors.py index e73e26456..88dabe87c 100644 --- a/lib/sqlalchemy/processors.py +++ b/lib/sqlalchemy/processors.py @@ -26,6 +26,12 @@ def str_to_datetime_processor_factory(regexp, type_): return type_(*map(int, rmatch(value).groups(0))) return process +def boolean_to_int(value): + if value is None: + return None + else: + return int(value) + try: from sqlalchemy.cprocessors import UnicodeResultProcessor, \ DecimalResultProcessor, \ diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index 9f322d1eb..111f2314b 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -1682,6 +1682,12 @@ class Boolean(TypeEngine, SchemaType): ) table.append_constraint(e) + def bind_processor(self, dialect): + if dialect.supports_native_boolean: + return None + else: + return processors.boolean_to_int + def result_processor(self, dialect, coltype): if dialect.supports_native_boolean: return None |