diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-06-21 17:23:14 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-06-21 17:23:14 +0000 |
| commit | c5e2d673a996a09e8cd399ebdde855773745b865 (patch) | |
| tree | 8ddf26c0404f59d79cf108a5ff922147794a0fec /lib/sqlalchemy/orm | |
| parent | 060c3ce33c02c2bcd78b54e05da8b9196a296b62 (diff) | |
| download | sqlalchemy-c5e2d673a996a09e8cd399ebdde855773745b865.tar.gz | |
- implemented [ticket:887], refresh readonly props upon save
- moved up "eager_defaults" active refresh step (this is an option used by just one user pretty much)
to be per-instance instead of per-table
- fixed table defs from previous deferred attributes enhancement
- CompositeColumnLoader equality comparison fixed for a/b == None; I suspect the composite capability in SA
needs a lot more work than this
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 62 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 3 |
2 files changed, 48 insertions, 17 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index da471b4d1..99e93df5d 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -138,7 +138,7 @@ class Mapper(object): self._clause_adapter = None self._requires_row_aliasing = False self.__inherits_equated_pairs = None - + if not issubclass(class_, object): raise sa_exc.ArgumentError("Class '%s' is not a new-style class" % class_.__name__) @@ -521,7 +521,15 @@ class Mapper(object): # ordering is important since it determines the ordering of mapper.primary_key (and therefore query.get()) self._pks_by_table[t] = util.OrderedSet(t.primary_key).intersection(pk_cols) self._cols_by_table[t] = util.OrderedSet(t.c).intersection(all_cols) - + + # determine cols that aren't expressed within our tables; + # mark these as "read only" properties which are refreshed upon + # INSERT/UPDATE + self._readonly_props = util.Set([ + self._columntoproperty[col] for col in all_cols if + not hasattr(col, 'table') or col.table not in self._cols_by_table + ]) + # if explicit PK argument sent, add those columns to the primary key mappings if self.primary_key_argument: for k in self.primary_key_argument: @@ -720,6 +728,13 @@ class Mapper(object): # columns (included in zblog tests) if col is None: col = prop.columns[0] + + # column is coming in after _readonly_props was initialized; check + # for 'readonly' + if hasattr(self, '_readonly_props') and \ + (not hasattr(col, 'table') or col.table not in self._cols_by_table): + self._readonly_props.add(prop) + else: # if column is coming in after _cols_by_table was initialized, ensure the col is in the # right set @@ -1169,10 +1184,27 @@ class Mapper(object): # testlib.pragma exempt:__hash__ inserted_objects.add((state, connection)) - + if not postupdate: - # call after_XXX extensions for state, mapper, connection, has_identity in tups: + + # expire readonly attributes + readonly = state.unmodified.intersection([ + p.key for p in chain(*[m._readonly_props for m in mapper.iterate_to_root()]) + ]) + + if readonly: + _expire_state(state, readonly) + + # if specified, eagerly refresh whatever has + # been expired. + if self.eager_defaults and state.unloaded: + state.key = self._identity_key_from_state(state) + uowtransaction.session.query(self)._get( + state.key, refresh_state=state, + only_load_props=state.unloaded) + + # call after_XXX extensions if not has_identity: if 'after_insert' in mapper.extension.methods: mapper.extension.after_insert(mapper, connection, state.obj()) @@ -1184,12 +1216,13 @@ class Mapper(object): sync.populate(state, self, state, self, self.__inherits_equated_pairs) def __postfetch(self, uowtransaction, connection, table, state, resultproxy, params, value_params): - """After an ``INSERT`` or ``UPDATE``, assemble newly generated - values on an instance. For columns which are marked as being generated - on the database side, set up a group-based "deferred" loader - which will populate those attributes in one query when next accessed. + """For a given Table that has just been inserted/updated, + mark as 'expired' those attributes which correspond to columns + that are marked as 'postfetch', and populate attributes which + correspond to columns marked as 'prefetch' or were otherwise generated + within _save_obj(). + """ - postfetch_cols = resultproxy.postfetch_cols() generated_cols = list(resultproxy.prefetch_cols()) @@ -1197,6 +1230,7 @@ class Mapper(object): po = table.corresponding_column(self.polymorphic_on) if po: generated_cols.append(po) + if self.version_id_col: generated_cols.append(self.version_id_col) @@ -1205,15 +1239,9 @@ class Mapper(object): self._set_state_attr_by_column(state, c, params[c.key]) deferred_props = [prop.key for prop in [self._columntoproperty[c] for c in postfetch_cols]] - + if deferred_props: - if self.eager_defaults: - state.key = self._identity_key_from_state(state) - uowtransaction.session.query(self)._get( - state.key, refresh_state=state, - only_load_props=deferred_props) - else: - _expire_state(state, deferred_props) + _expire_state(state, deferred_props) def _delete_obj(self, states, uowtransaction): """Issue ``DELETE`` statements for a list of objects. diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 829210205..66e9ccd97 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -96,6 +96,9 @@ class CompositeColumnLoader(ColumnLoader): return self.parent_property.composite_class(*obj.__composite_values__()) def compare(a, b): + if a is None or b is None: + return a is b + for col, aprop, bprop in zip(self.columns, a.__composite_values__(), b.__composite_values__()): |
