def decode_offset(self, params, offset): """Decode an "offset token" into appropriate query parameters. This essentially decodes the result of encode_offset(), adjusting the query parameters so that we can efficiently seek to the next available item based on the previous query. """ sort = params.get("sort", None) try: if sort == "index": # When sorting by sortindex, it's just a numeric offset. params["offset"] = int(offset) else: # When sorting by timestamp, it's a (bound, offset) pair. bound, offset = map(int, offset.split(":", 1)) # Queries with "newer" should always produce bound > newer if bound < params.get("newer", bound): raise InvalidOffsetError(offset) # Queries with "older" should always produce bound < older if bound > params.get("older", bound): raise InvalidOffsetError(offset) # The bound determines the starting point of the sort order. params["offset"] = offset if sort == "oldest": params["newer_eq"] = int(bound) else: params["older_eq"] = int(bound) except ValueError: raise InvalidOffsetError(offset)
def _find_items(self, session, userid, collection, **params): """Find items matching the given search parameters.""" params["userid"] = userid params["collectionid"] = self._get_collection_id(session, collection) if "ttl" not in params: params["ttl"] = int(session.timestamp) if "newer" in params: params["newer"] = ts2bigint(params["newer"]) # We always fetch one more item than necessary, so we can tell whether # there are additional items to be fetched with next_offset. limit = params.get("limit") if limit is not None: params["limit"] = limit + 1 offset = params.get("offset") if offset is not None: try: params["offset"] = offset = int(offset) except ValueError: raise InvalidOffsetError(offset) rows = session.query_fetchall("FIND_ITEMS", params) items = [self._row_to_bso(row) for row in rows] # If the query returned no results, we don't know whether that's # because it's empty or because it doesn't exist. Read the collection # timestamp and let it raise CollectionNotFoundError if necessary. if not items: self.get_collection_timestamp(session, userid, collection) # Check if we read past the original limit, set next_offset if so. next_offset = None if limit is not None and len(items) > limit: next_offset = (offset or 0) + limit items = items[:-1] return { "items": items, "next_offset": next_offset, }
def get_items(self, user, **kwds): # Decode kwds into individual filter values. newer = kwds.pop("newer", None) older = kwds.pop("older", None) limit = kwds.pop("limit", None) offset = kwds.pop("offset", None) sort = kwds.pop("sort", None) ids = kwds.pop("ids", None) for unknown_kwd in kwds: raise TypeError("Unknown keyword argument: %s" % (unknown_kwd,)) # Read all the items out of the cache. data, _ = self.get_cached_data(user) if data is None: raise CollectionNotFoundError # Restrict to certain item ids if specified. bsos_by_id = data["items"] if ids is not None: bsos = (bsos_by_id[item] for item in ids if item in bsos_by_id) else: bsos = bsos_by_id.itervalues() # Apply the various filters as generator expressions. if newer is not None: bsos = (bso for bso in bsos if bso["modified"] > newer) if older is not None: bsos = (bso for bso in bsos if bso["modified"] < older) # Filter out any that have expired. bsos = self._filter_expired_items(bsos) # Sort the resulting list. # We always sort so that offset/limit work correctly. # Using the id as a secondary key produces a unique ordering. bsos = list(bsos) if sort == "index": reverse = True key = bso_sort_key_index else: reverse = False if sort == "oldest" else True key = bso_sort_key_modified bsos.sort(key=key, reverse=reverse) # Trim to the specified offset, if any. # Note that we defaulted it to zero above. if offset is not None: try: offset = int(offset) except ValueError: raise InvalidOffsetError(offset) bsos = bsos[offset:] # Trim to the specified limit, if any. next_offset = None if limit is not None: if limit < len(bsos): bsos = bsos[:limit] next_offset = (offset or 0) + limit # Return the necessary information. return { "items": bsos, "next_offset": next_offset }
def decode_offset(self, params, offset): sort = params.get("sort", None) try: if sort == "index": # When sorting by sortindex, it's just a numeric offset. params["offset"] = int(offset) else: # When sorting by timestamp, it's a (bound, offset) pair. bound, offset = map(int, offset.split(":", 1)) bound = bigint2ts(bound) # Queries with "newer" should always produce bound > newer if bound < params.get("newer", bound): raise InvalidOffsetError(offset) # Queries with "older" should always produce bound < older if bound > params.get("older", bound): raise InvalidOffsetError(offset) # The bound determines the starting point of the sort order. params["offset"] = offset if sort == "oldest": params["newer_eq"] = bound else: params["older_eq"] = bound except ValueError: raise InvalidOffsetError(offset)