def _compare(self, other):
        """Compare this result to another result for sorting.

        Args:
            other (_Result): The other result to compare to.

        Returns:
            int: :data:`-1` if this result should come before `other`,
                :data:`0` if this result is equivalent to `other` for sorting
                purposes, or :data:`1` if this result should come after
                `other`.

        Raises:
            NotImplemented: If `order_by` was not passed to constructor or is
                :data:`None` or is empty.
            NotImplemented: If `other` is not a `_Result`.
        """
        if not self.order_by:
            raise NotImplementedError("Can't sort result set without order_by")

        if not isinstance(other, _Result):
            return NotImplemented

        for order in self.order_by:

            if order.name == "__key__":
                this_value = helpers.key_from_protobuf(
                    self.result_pb.entity.key).flat_path
                other_value = helpers.key_from_protobuf(
                    other.result_pb.entity.key).flat_path
            else:
                this_value_pb = self.result_pb.entity.properties[order.name]
                this_value = helpers._get_value_from_value_pb(this_value_pb)
                other_value_pb = other.result_pb.entity.properties[order.name]
                other_value = helpers._get_value_from_value_pb(other_value_pb)

                # Compare key paths if ordering by key property
                if isinstance(this_value, Key):
                    this_value = this_value.flat_path

                if isinstance(other_value, Key):
                    other_value = other_value.flat_path

            direction = -1 if order.reverse else 1

            if this_value < other_value:
                return -direction

            elif this_value > other_value:
                return direction

        return 0
Esempio n. 2
0
    def _matches_property_filter(
        self, stored_obj: _StoredObject, prop_filter: types.PropertyFilter
    ) -> bool:
        op = prop_filter.op
        name = prop_filter.property.name
        filter_val = ds_helpers._get_value_from_value_pb(prop_filter.value)

        # Handle ancestor queries. Only supports a single level for now
        if op == types.PropertyFilter.Operator.HAS_ANCESTOR:
            assert (
                name == "__key__"
                and prop_filter.value.WhichOneof("value_type") == "key_value"
            )
            assert len(prop_filter.value.key_value.path) == 1
            return (
                prop_filter.value.key_value.path[0] in stored_obj.entity.key.path[:-1]
            )

        # If the field we're looking for doesn't exist on this model, bail
        if name not in stored_obj.entity.properties:
            return False

        # Otherwise, compare the field against the value in the filter
        prop_val_pb = stored_obj.entity.properties[name]
        prop_val = ds_helpers._get_value_from_value_pb(prop_val_pb)
        method_name = self._OPERATOR_TO_CMP_METHOD_NAME.get(op)
        assert method_name

        prop_type = prop_val_pb.WhichOneof("value_type")
        if prop_type == "array_value":
            # For repeated properties, we need to unpack them
            res = any(getattr(p, method_name)(filter_val) is True for p in prop_val)
        elif filter_val is None:
            # Need to mimic python2 comparisons, where None is smaller than anything
            if op == types.PropertyFilter.Operator.GREATER_THAN_OR_EQUAL:
                res = True
            elif op == types.PropertyFilter.Operator.LESS_THAN_OR_EQUAL:
                res = prop_val is None
            elif op == types.PropertyFilter.Operator.LESS_THAN:
                res = False
            elif op == types.PropertyFilter.Operator.GREATER_THAN:
                res = prop_val is not None
            elif op == types.PropertyFilter.Operator.EQUAL:
                res = prop_val is None
            else:
                res = False
        else:
            res = getattr(prop_val, method_name)(filter_val)

        if res is NotImplemented:
            return False
        return res
    def _call_fut(self, pb):
        from google.cloud.datastore.helpers import _get_value_from_value_pb

        return _get_value_from_value_pb(pb)
    def _call_fut(self, pb):
        from google.cloud.datastore.helpers import _get_value_from_value_pb

        return _get_value_from_value_pb(pb)
Esempio n. 5
0
    def _run_query(
        self, request: types.RunQueryRequest, *args, **kwargs
    ) -> types.RunQueryResponse:
        # Don't support cloud sql
        # TODO also figire out error handling
        assert request.query

        # Query processing will be very naive.
        query: types.Query = request.query
        transaction_id: bytes = request.read_options.transaction
        resp_data: List[_StoredObject] = []
        for _, stored in self.store.items(transaction_id):
            if query.kind and stored.entity.key.path[-1].kind != query.kind[0].name:
                continue

            if self._matches_filter(stored, query.filter):
                resp_data.append(stored)

        if query.order:
            # TODO
            assert len(query.order) == 1
            order = query.order[0]
            assert order.direction in [
                types.PropertyOrder.Direction.DESCENDING,
                types.PropertyOrder.Direction.ASCENDING,
            ]
            resp_data.sort(
                key=lambda d: ds_helpers._get_value_from_value_pb(
                    d.entity.properties.get(order.property.name)
                ),
                reverse=order.direction == types.PropertyOrder.Direction.DESCENDING,
            )

        original_length = len(resp_data)
        skipped_results = 0
        results_after_limit = 0

        # Handle offset
        resp_data = resp_data[query.offset :]
        skipped_results = original_length - len(resp_data)

        if query.HasField("limit"):
            resp_data = resp_data[: query.limit.value]

        if query.projection:
            projection_fields = [p.property.name for p in query.projection]
            if projection_fields == ["__key__"]:
                result_type = types.EntityResult.ResultType.KEY_ONLY
                entity_results = [
                    types.EntityResult(
                        entity=types.Entity(key=resp.entity.key), version=resp.version
                    )
                    for resp in resp_data
                ]
            else:
                result_type = types.EntityResult.ResultType.PROJECTION
                entity_results = [
                    types.EntityResult(
                        entity=types.Entity(
                            key=resp.entity.key,
                            properties={
                                k: v
                                for k, v in resp.entity.properties.items()
                                if k in projection_fields
                            },
                        ),
                        version=resp.version,
                    )
                    for resp in resp_data
                ]
        else:
            result_type = types.EntityResult.ResultType.FULL
            entity_results = [
                types.EntityResult(entity=resp.entity, version=resp.version)
                for resp in resp_data
            ]

        if results_after_limit > 0:
            more_results = (
                types.QueryResultBatch.MoreResultsType.MORE_RESULTS_AFTER_LIMIT
            )
        else:
            more_results = types.QueryResultBatch.MoreResultsType.NO_MORE_RESULTS

        return types.RunQueryResponse(
            batch=types.QueryResultBatch(
                skipped_results=skipped_results,
                more_results=more_results,
                entity_result_type=result_type,
                entity_results=entity_results,
                snapshot_version=self.store.seqid(transaction_id),
            )
        )