Example #1
0
    def _update_entity(self, extent_name, oid, fields, related_entities,
                       rev=None):
        """Update an existing entity in an extent.

        - `extent_name`: Name of the extent to create a new entity in.

        - `oid`: OID of the entity to update.

        - `fields`: Dictionary of field_name:field_value mappings to change,
          where each field_value is the value to be stored in the database, as
          returned by a field instance's `_dump` method.

        - `related_entities`: Dictionary of field_name:related_entity_set
          mappings, where each related_entity_set is the set of entities
          stored in the field's structure, as returned by a field instance's
          `_entities_in_value` method.

        - `rev`: (optional) Specific revision to update the entity to.
        """
        # XXX: Could be optimized to update mappings only when
        # necessary.
        entity_classes = self._entity_classes
        entity_map, extent_map = self._entity_extent_map(extent_name, oid)
        field_name_id = extent_map['field_name_id']
        entity_field_ids = extent_map['entity_field_ids']
        extent_name_id = self._extent_name_id
        extent_maps_by_id = self._extent_maps_by_id
        indices_added = []
        indices_removed = []
        new_links = []
        links_created = []
        links_deleted = []
        ia_append = indices_added.append
        ir_append = indices_removed.append
        nl_append = new_links.append
        lc_append = links_created.append
        ld_append = links_deleted.append
        BTree = self._BTree
        try:
            # Get old values for use in a potential inversion.
            old_fields = self._entity_fields(extent_name, oid)
            old_related_entities = self._entity_related_entities(
                extent_name, oid)
            old_rev = entity_map['rev']
            # Dereference entities.
            for name, value in fields.items():
                field_id = field_name_id[name]
                if field_id in entity_field_ids:
                    if isinstance(value, Placeholder):
                        # Dereference entity.
                        other_extent_id = value.extent_id
                        other_oid = value.oid
                        value = (other_extent_id, other_oid)
                        nl_append((field_id, other_extent_id, other_oid))
                    elif len(related_entities.get(name, [])) > 1:
                        msg = (
                            'Field values with multiple entities are not '
                            'supported by format 1 Schevo databases.'
                            )
                        raise UnsupportedFieldType(reason)
                fields[name] = value
            # Get fields, and set UNASSIGNED for any fields that are
            # new since the last time the entity was stored.
            fields_by_id = entity_map['fields']
            all_field_ids = set(extent_map['field_id_name'].iterkeys())
            new_fields = all_field_ids - set(fields_by_id.iterkeys())
            fields_by_id.update(dict(
                (field_id, UNASSIGNED) for field_id in new_fields))
            # Remove existing index mappings.
            indices = extent_map['indices']
            for index_spec in indices.iterkeys():
                field_values = tuple(fields_by_id[field_id]
                                     for field_id in index_spec)
                # Find out if the index has been relaxed.
                relaxed_specs = self._relaxed[extent_name]
                if index_spec in relaxed_specs:
                    txns, relaxed = relaxed_specs[index_spec]
                else:
                    relaxed = None
                _index_remove(extent_map, index_spec, oid, field_values)
                ir_append((extent_map, index_spec, relaxed, oid, field_values))
            # Delete links from this entity to other entities.
            referrer_extent_id = extent_name_id[extent_name]
            for referrer_field_id in entity_field_ids:
                other_value = fields_by_id[referrer_field_id]
                if isinstance(other_value, tuple):
                    # Remove the link to the other entity.
                    other_extent_id, other_oid = other_value
                    link_key = (referrer_extent_id, referrer_field_id)
                    other_extent_map = extent_maps_by_id[other_extent_id]
                    other_entity_map = other_extent_map['entities'][other_oid]
                    links = other_entity_map['links']
                    other_links = links[link_key]
                    del other_links[oid]
                    other_entity_map['link_count'] -= 1
                    ld_append((other_entity_map, links, link_key, oid))
            # Create ephemeral fields for creating new index mappings.
            new_fields = dict(fields_by_id)
            for name, value in fields.iteritems():
                new_fields[field_name_id[name]] = value
            # Create new index mappings.
            for index_spec in indices.iterkeys():
                field_values = tuple(new_fields[field_id]
                                     for field_id in index_spec)
                # Find out if the index has been relaxed.
                relaxed_specs = self._relaxed[extent_name]
                if index_spec in relaxed_specs:
                    txns, relaxed = relaxed_specs[index_spec]
                else:
                    relaxed = None
                _index_add(extent_map, index_spec, relaxed, oid, field_values,
                           BTree)
                ia_append((extent_map, index_spec, oid, field_values))
            # Update links from this entity to another entity.
            referrer_extent_id = extent_name_id[extent_name]
            for referrer_field_id, other_extent_id, other_oid in new_links:
                other_extent_map = extent_maps_by_id[other_extent_id]
                try:
                    other_entity_map = other_extent_map['entities'][other_oid]
                except KeyError:
                    field_id_name = extent_map['field_id_name']
                    field_name = field_id_name[referrer_field_id]
                    other_extent_map = extent_maps_by_id[other_extent_id]
                    other_extent_name = other_extent_map['name']
                    raise error.EntityDoesNotExist(
                        other_extent_name, field_name=field_name)
                # Add a link to the other entity.
                links = other_entity_map['links']
                link_key = (referrer_extent_id, referrer_field_id)
                if link_key not in links:  # XXX Should already be there.
                    links[link_key] = BTree()
                links[link_key][oid] = None
                other_entity_map['link_count'] += 1
                lc_append((other_entity_map, links, link_key, oid))
            # Update actual fields.
            for name, value in fields.iteritems():
                fields_by_id[field_name_id[name]] = value
            if rev is None:
                entity_map['rev'] += 1
            else:
                entity_map['rev'] = rev
            # Allow inversion of this operation.
            self._append_inversion(
                self._update_entity, extent_name, oid, old_fields,
                old_related_entities, old_rev)
            # Keep track of changes.
            append_change = self._append_change
            append_change(UPDATE, extent_name, oid)
        except:
            # Revert changes made during update attempt.
            for _e, _i, _o, _f in indices_added:
                _index_remove(_e, _i, _o, _f)
            for _e, _i, _r, _o, _f in indices_removed:
                _index_add(_e, _i, _r, _o, _f, BTree)
            for other_entity_map, links, link_key, oid in links_created:
                del links[link_key][oid]
                other_entity_map['link_count'] -= 1
            for other_entity_map, links, link_key, oid in links_deleted:
                links[link_key][oid] = None
                other_entity_map['link_count'] += 1
            raise
Example #2
0
    def _create_entity(self, extent_name, fields, related_entities,
                       oid=None, rev=None):
        """Create a new entity in an extent; return the oid.

        - `extent_name`: Name of the extent to create a new entity in.

        - `fields`: Dictionary of field_name:field_value mappings, where
          each field_value is the value to be stored in the database, as
          returned by a field instance's `_dump` method.

        - `related_entities`: Dictionary of field_name:related_entity_set
          mappings, where each related_entity_set is the set of entities
          stored in the field's structure, as returned by a field
          instance's `_entities_in_value` method.

        - `oid`: (optional) Specific OID to create the entity as; used
          for importing data, e.g. from an XML document.

        - `rev`: (optional) Specific revision to create the entity as; see
          `oid`.
        """
        extent_map = self._extent_map(extent_name)
        entities = extent_map['entities']
        old_next_oid = extent_map['next_oid']
        field_name_id = extent_map['field_name_id']
        entity_field_ids = extent_map['entity_field_ids']
        extent_name_id = self._extent_name_id
        extent_maps_by_id = self._extent_maps_by_id
        indices_added = []
        ia_append = indices_added.append
        links_created = []
        lc_append = links_created.append
        BTree = self._BTree
        PDict = self._PDict
        try:
            if oid is None:
                oid = extent_map['next_oid']
                extent_map['next_oid'] += 1
            if rev is None:
                rev = 0
            if oid in entities:
                raise error.EntityExists(extent_name, oid)
            # Create dict with field-id:field-value items.
            fields_by_id = PDict()
            new_links = []
            nl_append = new_links.append
            for name, value in fields.iteritems():
                field_id = field_name_id[name]
                # Handle entity reference fields.
                if field_id in entity_field_ids:
                    if isinstance(value, Placeholder):
                        # Dereference entity.
                        other_extent_id = value.extent_id
                        other_oid = value.oid
                        value = (other_extent_id, other_oid)
                        nl_append((field_id, other_extent_id, other_oid))
                    elif len(related_entities.get(name, [])) > 1:
                        msg = (
                            'Field values with multiple entities are not '
                            'supported by format 1 Schevo databases.'
                            )
                        raise error.UnsupportedFieldType(reason)
                fields_by_id[field_id] = value
            # Make sure fields that weren't specified are set to
            # UNASSIGNED.
            setdefault = fields_by_id.setdefault
            for field_id in field_name_id.itervalues():
                setdefault(field_id, UNASSIGNED)
            # Update index mappings.
            indices = extent_map['indices']
            for index_spec in indices.iterkeys():
                field_values = tuple(fields_by_id[field_id]
                                     for field_id in index_spec)
                # Find out if the index has been relaxed.
                relaxed_specs = self._relaxed[extent_name]
                if index_spec in relaxed_specs:
                    txns, relaxed = relaxed_specs[index_spec]
                else:
                    relaxed = None
                _index_add(extent_map, index_spec, relaxed, oid, field_values,
                           BTree)
                ia_append((extent_map, index_spec, oid, field_values))
            # Update links from this entity to another entity.
            referrer_extent_id = extent_name_id[extent_name]
            for referrer_field_id, other_extent_id, other_oid in new_links:
                other_extent_map = extent_maps_by_id[other_extent_id]
                try:
                    other_entity_map = other_extent_map['entities'][other_oid]
                except KeyError:
                    field_id_name = extent_map['field_id_name']
                    field_name = field_id_name[referrer_field_id]
                    other_extent_map = extent_maps_by_id[other_extent_id]
                    other_extent_name = other_extent_map['name']
                    raise error.EntityDoesNotExist(
                        other_extent_name, field_name=field_name)
                # Add a link to the other entity.
                links = other_entity_map['links']
                link_key = (referrer_extent_id, referrer_field_id)
                if link_key not in links:  # XXX Should already be there.
                    links[link_key] = BTree()
                links[link_key][oid] = None
                other_entity_map['link_count'] += 1
                lc_append((other_entity_map, links, link_key, oid))
            # Create the actual entity.
            entity_map = entities[oid] = PDict()
            entity_map['rev'] = rev
            entity_map['fields'] = fields_by_id
            # XXX flesh out links based on who is capable of linking
            # to this one.
            entity_map['link_count'] = 0
            entity_map['links'] = PDict()
            extent_map['len'] += 1
            # Allow inversion of this operation.
            self._append_inversion(self._delete_entity, extent_name, oid)
            # Keep track of changes.
            append_change = self._append_change
            append_change(CREATE, extent_name, oid)
            return oid
        except:
            # Revert changes made during create attempt.
            for _e, _i, _o, _f in indices_added:
                _index_remove(_e, _i, _o, _f)
            for other_entity_map, links, link_key, oid in links_created:
                del links[link_key][oid]
                other_entity_map['link_count'] -= 1
            extent_map['next_oid'] = old_next_oid
            raise