def validate_commit_req(self, req): """Validates a normalized CommitRequest. Args: req: a googledatastore.CommitRequest Raises: ValidationError: if the request is invalid """ _assert_initialized(req) if (req.mode == googledatastore.CommitRequest.MODE_UNSPECIFIED or req.mode == googledatastore.CommitRequest.TRANSACTIONAL): _assert_condition(req.WhichOneof('transaction_selector'), 'Transactional commit requires a transaction.') if req.WhichOneof('transaction_selector') == 'transaction': _assert_condition( req.transaction, 'a transaction cannot be the empty ' 'string.') seen_base_versions = {} for mutation in req.mutations: v1_key, _ = datastore_pbs.get_v1_mutation_key_and_entity( mutation) if datastore_pbs.is_complete_v1_key(v1_key): mutation_base_version = None if mutation.HasField('base_version'): mutation_base_version = mutation.base_version key = datastore_types.ReferenceToKeyValue( v1_key, self.__id_resolver) if key in seen_base_versions: _assert_condition( seen_base_versions[key] == mutation_base_version, 'Mutations for the same entity must have the ' 'same base version.') seen_base_versions[key] = mutation_base_version elif req.mode == googledatastore.CommitRequest.NON_TRANSACTIONAL: _assert_condition( not req.WhichOneof('transaction_selector'), 'Non-transactional commit cannot specify a ' 'transaction.') seen_complete_keys = set() for mutation in req.mutations: v1_key, _ = datastore_pbs.get_v1_mutation_key_and_entity( mutation) if datastore_pbs.is_complete_v1_key(v1_key): key = datastore_types.ReferenceToKeyValue( v1_key, self.__id_resolver) _assert_condition( key not in seen_complete_keys, 'A non-transactional commit may not contain ' 'multiple mutations affecting the same entity.') seen_complete_keys.add(key) for mutation in req.mutations: self.__validate_mutation(mutation)
def validate_commit_req(self, req): """Validates a normalized CommitRequest. Args: req: a googledatastore.CommitRequest Raises: ValidationError: if the request is invalid """ _assert_initialized(req) if (req.mode == googledatastore.CommitRequest.MODE_UNSPECIFIED or req.mode == googledatastore.CommitRequest.TRANSACTIONAL): _assert_condition(req.WhichOneof('transaction_selector'), 'Transactional commit requires a transaction.') if req.WhichOneof('transaction_selector') == 'transaction': _assert_condition(req.transaction, 'a transaction cannot be the empty ' 'string.') seen_base_versions = {} for mutation in req.mutations: v1_key, _ = datastore_pbs.get_v1_mutation_key_and_entity(mutation) if datastore_pbs.is_complete_v1_key(v1_key): mutation_base_version = None if mutation.HasField('base_version'): mutation_base_version = mutation.base_version key = datastore_types.ReferenceToKeyValue(v1_key, self.__id_resolver) if key in seen_base_versions: _assert_condition(seen_base_versions[key] == mutation_base_version, 'Mutations for the same entity must have the ' 'same base version.') seen_base_versions[key] = mutation_base_version elif req.mode == googledatastore.CommitRequest.NON_TRANSACTIONAL: _assert_condition(not req.WhichOneof('transaction_selector'), 'Non-transactional commit cannot specify a ' 'transaction.') seen_complete_keys = set() for mutation in req.mutations: v1_key, _ = datastore_pbs.get_v1_mutation_key_and_entity(mutation) if datastore_pbs.is_complete_v1_key(v1_key): key = datastore_types.ReferenceToKeyValue(v1_key, self.__id_resolver) _assert_condition(key not in seen_complete_keys, 'A non-transactional commit may not contain ' 'multiple mutations affecting the same entity.') seen_complete_keys.add(key) for mutation in req.mutations: self.__validate_mutation(mutation)
def __commit(self, v1_mutations, v1_txn, resp): """Commits a list of v1 mutations. Args: v1_mutations: the list of mutations to apply and commit v1_txn: required v1 transaction handle in which to apply the mutations resp: a v1 CommitResponse to update with the result of this commit """ mutation_keys = [] seen_keys = set() allocated_keys = {} conflict_cache = {} version_cache = {} for i, mutation in enumerate(v1_mutations): v1_key, v1_entity = datastore_pbs.get_v1_mutation_key_and_entity(mutation) key = datastore_types.ReferenceToKeyValue(v1_key, self._id_resolver) if not datastore_pbs.is_complete_v1_key(v1_key): v1_key = self.__put_v1_entity(v1_entity, v1_txn) key = datastore_types.ReferenceToKeyValue(v1_key, self._id_resolver) allocated_keys[key] = v1_key elif key not in conflict_cache: base_version = None if mutation.HasField("base_version") and key not in seen_keys: base_version = mutation.base_version conflict_version = self.__apply_v1_mutation(mutation, base_version, v1_txn, version_cache) if conflict_version is not None: conflict_cache[key] = conflict_version mutation_keys.append(key) seen_keys.add(key) v3_txn = datastore_pb.Transaction() self.__service_converter.v1_to_v3_txn(v1_txn, v3_txn) v3_resp = datastore_pb.CommitResponse() self.__make_v3_call("Commit", v3_txn, v3_resp) resp.index_updates = v3_resp.cost().index_writes() mutation_versions = {} for version in v3_resp.version_list(): key = datastore_types.ReferenceToKeyValue(version.root_entity_key()) mutation_versions[key] = version.version() for key in mutation_keys: mutation_result = resp.mutation_results.add() if key in allocated_keys: mutation_result.key.CopyFrom(allocated_keys[key]) if key in conflict_cache: mutation_result.conflict_detected = True mutation_result.version = conflict_cache[key] else: mutation_result.version = mutation_versions[key]
def __apply_v1_mutation(self, v1_mutation, base_version, v1_txn, version_cache): """Applies a v1 Mutation in a transaction. Args: v1_mutation: a googledatastore.Mutation, must be for a complete key. base_version: optional, the version the entity is expected to be at. If the entity has a different version number, the mutation does not apply. If None, then this check is skipped. v1_txn: a v1 transaction handle version_cache: a cache of entity keys to version, for entities that have been mutated previously in this transaction. """ v1_key, v1_entity = datastore_pbs.get_v1_mutation_key_and_entity( v1_mutation) key = datastore_types.ReferenceToKeyValue(v1_key, self._id_resolver) if (v1_mutation.HasField('insert') or v1_mutation.HasField('update') or base_version is not None) and key not in version_cache: version_cache[key] = self.__get_v1_entity_version(v1_key, v1_txn) if v1_mutation.HasField('insert'): if base_version is not None and base_version != _NO_VERSION: raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, 'Cannot insert an entity with a ' 'base version greater than zero') elif version_cache[key] != _NO_VERSION: raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, 'Entity already exists.') elif v1_mutation.HasField('update'): if base_version is not None and base_version == _NO_VERSION: raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, 'Cannot update an entity with a ' 'base version set to zero') elif version_cache[key] == _NO_VERSION: raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, 'Entity does not exist.') if base_version is not None: persisted_version = version_cache[key] if persisted_version != _NO_VERSION and persisted_version < base_version: raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, 'Invalid base version, it is ' 'greater than the stored ' 'version') if persisted_version != base_version: return persisted_version if v1_mutation.HasField('delete'): self.__delete_v1_key(v1_key, v1_txn) version_cache[key] = _NO_VERSION else: self.__put_v1_entity(v1_entity, v1_txn) version_cache[key] = _MINIMUM_VERSION
def __commit(self, v1_mutations, v1_txn, resp): """Commits a list of v1 mutations. Args: v1_mutations: the list of mutations to apply and commit v1_txn: required v1 transaction handle in which to apply the mutations resp: a v1 CommitResponse to update with the result of this commit """ mutation_keys = [] seen_keys = set() allocated_keys = {} conflict_cache = {} version_cache = {} for i, mutation in enumerate(v1_mutations): v1_key, v1_entity = datastore_pbs.get_v1_mutation_key_and_entity(mutation) key = datastore_types.ReferenceToKeyValue(v1_key, self._id_resolver) if not datastore_pbs.is_complete_v1_key(v1_key): v1_key = self.__put_v1_entity(v1_entity, v1_txn) key = datastore_types.ReferenceToKeyValue(v1_key, self._id_resolver) allocated_keys[key] = v1_key elif key not in conflict_cache: base_version = None if mutation.HasField('base_version') and key not in seen_keys: base_version = mutation.base_version conflict_version = self.__apply_v1_mutation(mutation, base_version, v1_txn, version_cache) if conflict_version is not None: conflict_cache[key] = conflict_version mutation_keys.append(key) seen_keys.add(key) v3_txn = datastore_pb.Transaction() self.__service_converter.v1_to_v3_txn(v1_txn, v3_txn) v3_resp = datastore_pb.CommitResponse() self.__make_v3_call('Commit', v3_txn, v3_resp) resp.index_updates = v3_resp.cost().index_writes() mutation_versions = {} for version in v3_resp.version_list(): key = datastore_types.ReferenceToKeyValue(version.root_entity_key()) mutation_versions[key] = version.version() for key in mutation_keys: mutation_result = resp.mutation_results.add() if key in allocated_keys: mutation_result.key.CopyFrom(allocated_keys[key]) if key in conflict_cache: mutation_result.conflict_detected = True mutation_result.version = conflict_cache[key] else: mutation_result.version = mutation_versions[key]