diff --git a/gramps/gen/db/base.py b/gramps/gen/db/base.py index 72d536b5d..addc8d19d 100644 --- a/gramps/gen/db/base.py +++ b/gramps/gen/db/base.py @@ -1892,6 +1892,7 @@ class DbWriteBase(DbReadBase): ["AND", [filter, filter, ...]] | ["OR", [filter, filter, ...]] | ["NOT", filter] + order_by - [[fieldname, "ASC" | "DESC"], ...] """ class Result(list): """ @@ -1993,9 +1994,7 @@ class DbWriteBase(DbReadBase): if "*" in fields: fields.remove("*") fields.extend(self._tables[table]["class_func"].get_schema().keys()) - #FIXME: add order_by to iter_funcs - #data = self._tables[table]["iter_func"](order_by=order_by) - data = self._tables[table]["iter_func"]() + data = self._tables[table]["iter_func"](order_by=order_by) position = 0 selected = 0 result = Result() @@ -2041,3 +2040,14 @@ class DbWriteBase(DbReadBase): """ name = self._tables[table]["class_func"].get_field_alias(name) return name.replace(".", "__") + + def eval_order_by(self, order_by, obj): + """ + Given a list of [[field, DIRECTION], ...] + return the list of values of the fields + """ + values = [] + for (field, direction) in order_by: + values.append(obj.get_field(field)) + return values + diff --git a/gramps/gen/db/generic.py b/gramps/gen/db/generic.py index c0571f073..c7b3baa69 100644 --- a/gramps/gen/db/generic.py +++ b/gramps/gen/db/generic.py @@ -33,6 +33,7 @@ import logging import shutil import bisect import ast +from operator import itemgetter #------------------------------------------------------------------------ # @@ -1143,11 +1144,36 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: return None - def iter_people(self): - return (Person.create(data[1]) for data in self.get_person_cursor()) + def iter_items(self, order_by, class_): + """ + Iterate over items in a class, possibly ordered by + a list of field names and direction ("ASC" or "DESC"). + """ + cursor = self._tables[class_.__name__]["cursor_func"] + if order_by is None: + for data in cursor(): + yield class_.create(data[1]) + else: + # first build sort order: + sorted_items = [] + for data in cursor(): + obj = class_.create(data[1]) + # just use values and handle to keep small: + sorted_items.append((self.eval_order_by(order_by, obj), obj.handle)) + # next we sort by fields and direction + pos = len(order_by) - 1 + for (field, order) in reversed(order_by): # sort the lasts parts first + sorted_items.sort(key=itemgetter(pos), reverse=(order=="DESC")) + pos -= 1 + # now we will look them up again: + for (order_by_values, handle) in sorted_items: + yield self._tables[class_.__name__]["handle_func"](handle) - def iter_families(self): - return (Family.create(data[1]) for data in self.get_family_cursor()) + def iter_people(self, order_by=None): + return self.iter_items(order_by, Person) + + def iter_families(self, order_by=None): + return self.iter_items(order_by, Family) def get_person_from_gramps_id(self, gramps_id): return Person.create(self.person_id_map[gramps_id]) @@ -1817,26 +1843,26 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback): def is_open(self): return self.db_is_open - def iter_citations(self): - return (Citation.create(data[1]) for data in self.get_citation_cursor()) + def iter_citations(self, order_by=None): + return self.iter_items(order_by, Citation) - def iter_events(self): - return (Event.create(data[1]) for data in self.get_event_cursor()) + def iter_events(self, order_by=None): + return self.iter_items(order_by, Event) - def iter_media(self): - return (Media.create(data[1]) for data in self.get_media_cursor()) + def iter_media(self, order_by=None): + return self.iter_items(order_by, Media) - def iter_notes(self): - return (Note.create(data[1]) for data in self.get_note_cursor()) + def iter_notes(self, order_by=None): + return self.iter_items(order_by, Note) - def iter_places(self): - return (Place.create(data[1]) for data in self.get_place_cursor()) + def iter_places(self, order_by=None): + return self.iter_items(order_by, Place) - def iter_repositories(self): - return (Repository.create(data[1]) for data in self.get_repository_cursor()) + def iter_repositories(self, order_by=None): + return self.iter_items(order_by, Repository) - def iter_sources(self): - return (Source.create(data[1]) for data in self.get_source_cursor()) + def iter_sources(self, order_by=None): + return self.iter_items(order_by, Source) def iter_tags(self): return (Tag.create(data[1]) for data in self.get_tag_cursor()) diff --git a/gramps/plugins/database/bsddb_support/read.py b/gramps/plugins/database/bsddb_support/read.py index 949f8d9d2..def8bb049 100644 --- a/gramps/plugins/database/bsddb_support/read.py +++ b/gramps/plugins/database/bsddb_support/read.py @@ -34,6 +34,7 @@ import time import random import os from sys import maxsize +from operator import itemgetter try: from bsddb3 import db @@ -1203,12 +1204,34 @@ class DbBsddbRead(DbReadBase, Callback): """ Closure that returns an iterator over objects in the database. """ - def g(self): - with curs_(self) as cursor: - for key, data in cursor: - obj = obj_() - obj.unserialize(data) - yield obj + def g(self, order_by=None): + """ + order_by - [[field, DIRECTION], ...] + DIRECTION is "ASC" or "DESC" + """ + if order_by is None: + with curs_(self) as cursor: + for key, data in cursor: + obj = obj_() + obj.unserialize(data) + yield obj + else: + # first build sort order: + sorted_items = [] + with curs_(self) as cursor: + for key, data in cursor: + obj = obj_() + obj.unserialize(data) + # just use values and handle to keep small: + sorted_items.append((self.eval_order_by(order_by, obj), obj.handle)) + # next we sort by fields and direction + pos = len(order_by) - 1 + for (field, order) in reversed(order_by): # sort the lasts parts first + sorted_items.sort(key=itemgetter(pos), reverse=(order=="DESC")) + pos -= 1 + # now we will look them up again: + for (order_by_values, handle) in sorted_items: + yield self._tables[obj_.__class__.__name__]["handle_func"](handle) return g # Use closure to define iterators for each primary object type