Ejemplo n.º 1
0
def run(database, document, date):
    """
    Display people probably alive and their ages on a particular date.
    """
    # setup the simple access functions
    sdb = SimpleAccess(database)
    sdoc = SimpleDoc(document)
    stab = QuickTable(sdb)
    if not date.get_valid():
        sdoc.paragraph("Date is not a valid date.")
        return
    # display the title
    if date.get_day_valid():
        sdoc.title(_("People and their ages the %s") % displayer.display(date))
    else:
        sdoc.title(_("People and their ages on %s") % displayer.display(date))
    stab.columns(_("Person"), _("Age"),
                 _("Status"))  # Actual Date makes column unicode
    alive_matches = 0
    dead_matches = 0
    for person in sdb.all_people():
        alive, birth, death, explain, relative = \
            probably_alive(person, database, date, return_range=True)
        # Doesn't show people probably alive but no way of figuring an age:
        if alive:
            if birth:
                diff_span = (date - birth)
                stab.row(person, str(diff_span), _("Alive: %s") % explain)
                stab.row_sort_val(1, int(diff_span))
            else:
                stab.row(person, "", _("Alive: %s") % explain)
                stab.row_sort_val(1, 0)
            alive_matches += 1
        else:  # not alive
            if birth:
                diff_span = (date - birth)
                stab.row(person, str(diff_span), _("Deceased: %s") % explain)
                stab.row_sort_val(1, int(diff_span))
            else:
                stab.row(person, "", _("Deceased: %s") % explain)
                stab.row_sort_val(1, 1)
            dead_matches += 1

    document.has_data = (alive_matches + dead_matches) > 0
    sdoc.paragraph(
        _("\nLiving matches: %(alive)d, "
          "Deceased matches: %(dead)d\n") % {
              'alive': alive_matches,
              'dead': dead_matches
          })
    if document.has_data:
        stab.write(sdoc)
    sdoc.paragraph("")
Ejemplo n.º 2
0
def run(database, document, date):
    """
    Display people probably alive and their ages on a particular date.
    """
    # setup the simple access functions
    sdb = SimpleAccess(database)
    sdoc = SimpleDoc(document)
    stab = QuickTable(sdb)
    if not date.get_valid():
        sdoc.paragraph("Date is not a valid date.")
        return
    # display the title
    if date.get_day_valid():
        sdoc.title(_("People and their ages the %s") %
               displayer.display(date))
    else:
        sdoc.title(_("People and their ages on %s") %
               displayer.display(date))
    stab.columns(_("Person"), _("Age"), _("Status")) # Actual Date makes column unicode
    alive_matches = 0
    dead_matches = 0
    for person in sdb.all_people():
        alive, birth, death, explain, relative = \
            probably_alive(person, database, date, return_range=True)
        # Doesn't show people probably alive but no way of figuring an age:
        if alive:
            if birth:
                diff_span = (date - birth)
                stab.row(person, str(diff_span), _("Alive: %s") % explain)
                stab.row_sort_val(1, int(diff_span))
            else:
                stab.row(person, "", _("Alive: %s") % explain)
                stab.row_sort_val(1, 0)
            alive_matches += 1
        else: # not alive
            if birth:
                diff_span = (date - birth)
                stab.row(person, str(diff_span), _("Deceased: %s") % explain)
                stab.row_sort_val(1, int(diff_span))
            else:
                stab.row(person, "", _("Deceased: %s") % explain)
                stab.row_sort_val(1, 1)
            dead_matches += 1

    document.has_data = (alive_matches + dead_matches) > 0
    sdoc.paragraph(_("\nLiving matches: %(alive)d, "
                     "Deceased matches: %(dead)d\n") %
                         {'alive' : alive_matches, 'dead' : dead_matches})
    if document.has_data:
        stab.write(sdoc)
    sdoc.paragraph("")
Ejemplo n.º 3
0
class DBI(object):
    """
    The SQL-like interface to the database and document instances.
    """
    def __init__(self, database, document=None):
        self.database = database
        self.document = document
        self.data = {}
        self.select = 0
        self.flat = False
        self.raw = False
        if self.database:
            for name in ('Person', 'Family', 'Event', 'Place', 'Repository',
                         'Source', 'Citation', 'Media', 'Note', 'Tag'):
                d = self.database._tables[name]["class_func"]().to_struct()
                self.data[name.lower()] = d.keys()
        # The macros:
        self.shortcuts = {
            "SURNAME": "primary_name.surname_list[0].surname",
            "GIVEN": "primary_name.first_name",
        }

    def parse(self, query):
        """
        Parse the query.
        """
        self.query_text = query.replace("\n", " ").strip()
        lexed = self.lexer(self.query_text)
        #print(lexed)
        self.parser(lexed)
        for col_name in self.columns[:]:  # copy
            if col_name == "*":
                self.columns.remove('*')
                # this is useful to see what _class it is:
                self.columns.extend(self.get_columns(self.table))
                # otherwise remove metadata:
                #self.columns.extend([col for col in self.get_columns(self.table) if not col.startswith("_"))

    def lexer(self, string):
        """
        Given a string, break into a list of chunks.
        """
        retval = []
        state = None
        current = ""
        stack = []
        i = 0
        # Handle macros:
        for key in self.shortcuts.keys():
            string = string.replace(key, self.shortcuts[key])
        # (some "expression" in ok)
        # func(some "expression" in (ok))
        # list[1][0]
        # [x for x in list]
        while i < len(string):
            ch = string[i]
            #print("lex:", i, ch, state, retval, current)
            if state == "in-double-quote":
                if ch == '"':
                    state = stack.pop()
                current += ch
            elif state == "in-single-quote":
                if ch == "'":
                    state = stack.pop()
                current += ch
            elif state == "in-expr":
                if ch == ")":
                    state = stack.pop()
                elif ch == "(":
                    stack.append("in-expr")
                current += ch
            elif state == "in-square-bracket":
                if ch == "]":
                    state = stack.pop()
                elif ch == "[":
                    stack.append("in-square-bracket")
                current += ch
            elif ch == '"':
                stack.append(state)
                state = "in-double-quote"
                current += ch
            elif ch == "'":
                stack.append(state)
                state = "in-single-quote"
                current += ch
            elif ch == "(":
                stack.append(state)
                state = "in-expr"
                current += "("
            elif ch == "[":
                stack.append(state)
                state = "in-square-bracket"
                current += "["
            elif ch == ",":
                if current:
                    retval.append(current)
                retval.append(",")
                current = ""
            elif ch == "=":
                if current:
                    retval.append(current)
                retval.append("=")
                current = ""
            elif ch in [' ', '\t', '\n', ";"]:  # break
                if current:
                    retval.append(current)
                    if current.upper() == "WHERE":
                        # HACK: get rest of string:
                        if string[-1] == ";":
                            retval.append(string[i + 1:-1])
                            i = len(string) - 2
                        else:
                            retval.append(string[i + 1:])
                            i = len(string) - 1
                    current = ""
                else:
                    pass  # ignore whitespace
            else:
                current += ch
            i += 1
        if current:
            retval.append(current)
        #print("lexed:", retval)
        return retval

    def parser(self, lex):
        """
        Takes output of lexer, and sets the values.
        After parser, the DBI will be ready to process query.
        """
        self.action = None
        self.table = None
        self.columns = []
        self.setcolumns = []
        self.values = []
        self.aliases = {}
        self.limit = None
        self.where = None
        self.index = 0
        while self.index < len(lex):
            symbol = lex[self.index]
            if symbol.upper() == "FROM":
                # from table select *;
                self.index += 1
                if self.index < len(lex):
                    self.table = lex[self.index]
            elif symbol.upper() == "SELECT":
                # select a, b from table;
                self.action = "SELECT"
                self.index += 1
                self.columns.append(lex[self.index])
                self.index += 1
                while self.index < len(lex) and lex[self.index].upper() in [
                        ",", "AS"
                ]:
                    sep = lex[self.index]
                    if sep == ",":
                        self.index += 1
                        self.columns.append(lex[self.index])
                        self.index += 1
                    elif sep.upper() == "AS":
                        self.index += 1  # alias
                        self.aliases[self.columns[-1]] = lex[self.index]
                        self.index += 1
                self.index -= 1
            elif symbol.upper() == "DELETE":
                # delete from table where item == 1;
                self.action = "DELETE"
                self.columns = ["gramps_id"]
            elif symbol.upper() == "SET":
                # SET x=1, y=2
                self.index += 1
                self.setcolumns.append(lex[self.index])  # first column
                self.index += 1  # equal sign
                # =
                self.index += 1  # value
                self.values.append(lex[self.index])
                self.index += 1  # comma
                while self.index < len(lex) and lex[self.index] == ",":
                    self.index += 1  # next column
                    self.setcolumns.append(lex[self.index])
                    self.index += 1  # equal
                    # =
                    self.index += 1  # value
                    self.values.append(lex[self.index])
                    self.index += 1  # comma?
                self.index -= 1
            elif symbol.upper() == "LIMIT":
                self.index += 1  # after LIMIT
                number = lex[self.index]
                self.index += 1  # maybe a comma
                if self.index < len(lex) and lex[self.index] == ",":
                    self.index += 1  # after ","
                    stop = lex[self.index]
                    self.limit = (int(number), int(stop))
                else:
                    self.limit = (0, int(number))
                    self.index -= 1
            elif symbol.upper() == "WHERE":
                # how can we get all of Python expressions?
                # this assumes all by ;
                self.index += 1
                self.where = lex[self.index]
            elif symbol.upper() == "UPDATE":
                # update table set x=1, y=2 where condition;
                # UPDATE gramps_id set tag_list = Tag("Betty") from person where  "Betty" in primary_name.first_name
                self.columns = ["gramps_id"]
                self.action = "UPDATE"
                if self.index < len(lex):
                    self.index += 1
                    self.table = lex[self.index]
            elif symbol.upper() == "FLAT":
                self.flat = True
            elif symbol.upper() == "EXPAND":
                self.flat = False
            elif symbol.upper() == "RAW":
                self.raw = True
            elif symbol.upper() == "NORAW":
                self.raw = False
            else:
                raise AttributeError("invalid SQL expression: '... %s ...'" %
                                     symbol)
            self.index += 1

    def close(self):
        """
        Close up any progress widgets or dialogs.
        """
        #try:
        #    self.progress.close()
        #except:
        pass

    def clean_titles(self, columns):
        """
        Take the columns and turn them into strings for the table.
        """
        retval = []
        for column in columns:
            if column in self.aliases:
                column = self.aliases[column]
            retval.append(column.replace("_", "__"))
        return retval

    def query(self, query):
        self.parse(query)
        self.select = 0
        start_time = time.time()

        class Table():
            results = []

            def row(self, *args, **kwargs):
                self.results.append([args, kwargs])

            def get_rows(self):
                return [list(item[0]) for item in self.results]

        table = Table()
        self.sdb = SimpleAccess(self.database)
        self.process_table(table)  # a class that has .row(1, 2, 3, ...)
        print(
            _("%d rows processed in %s seconds.\n") %
            (self.select, time.time() - start_time))
        return table

    def eval(self):
        """
        Execute the query.
        """
        self.sdb = SimpleAccess(self.database)
        self.stab = QuickTable(self.sdb)
        self.select = 0
        start_time = time.time()
        self.process_table(self.stab)  # a class that has .row(1, 2, 3, ...)
        if self.select > 0:
            self.stab.columns(*self.clean_titles(self.columns))
            self.sdoc = SimpleDoc(self.document)
            self.sdoc.title(self.query_text)
            self.sdoc.paragraph("\n")
            self.sdoc.paragraph("%d rows processed in %s seconds.\n" %
                                (self.select, time.time() - start_time))
            self.stab.write(self.sdoc)
            self.sdoc.paragraph("")
        return _("%d rows processed in %s seconds.\n") % (
            self.select, time.time() - start_time)

    def get_columns(self, table):
        """
        Get the columns for the given table.
        """
        if self.database:
            retval = self.data[table.lower()]
            return retval  # [self.name] + retval
        else:
            return ["*"]

    def process_table(self, table):
        """
        Given a table name, process the query on the elements of the
        table.
        """
        # 'Person', 'Family', 'Source', 'Citation', 'Event', 'Media',
        # 'Place', 'Repository', 'Note', 'Tag'
        # table: a class that has .row(1, 2, 3, ...)
        if self.table == "person":
            self.do_query(self.sdb.all_people(), table)
        elif self.table == "family":
            self.do_query(self.sdb.all_families(), table)
        elif self.table == "event":
            self.do_query(self.sdb.all_events(), table)
        elif self.table == "source":
            self.do_query(self.sdb.all_sources(), table)
        elif self.table == "tag":
            self.do_query(self.sdb.all_tags(), table)
        elif self.table == "citation":
            self.do_query(self.sdb.all_citations(), table)
        elif self.table == "media":
            self.do_query(self.sdb.all_media(), table)
        elif self.table == "place":
            self.do_query(self.sdb.all_places(), table)
        elif self.table == "repository":
            self.do_query(self.sdb.all_repositories(), table)
        elif self.table == "note":
            self.do_query(self.sdb.all_notes(), table)
        else:
            raise AttributeError("no such table: '%s'" % self.table)

    def get_tag(self, name):
        tag = self.database.get_tag_from_name(name)
        if tag is None:
            tag = gramps.gen.lib.Tag()
            tag.set_name(name)
            trans_class = self.database.get_transaction_class()
            with trans_class("QueryQuickview new Tag",
                             self.database,
                             batch=False) as trans:
                self.database.add_tag(tag, trans)
        return Handle("Tag", tag.handle)

    def make_env(self, **kwargs):
        """
        An environment with which to eval elements.
        """
        retval = Environment({
            _("Date"): gramps.gen.lib.date.Date,
            _("Today"): gramps.gen.lib.date.Today(),
            "random": random,
            "re": re,
            "db": self.database,
            "sdb": self.sdb,
            "lib": gramps.gen.lib,
            "_": _,
            "Tag": self.get_tag,
        })
        retval.update(__builtins__)
        retval.update(kwargs)
        return retval

    def stringify(self, value):
        """
        Turn the value into an appropriate string representation.
        """
        if self.raw:
            return value
        if isinstance(value, Struct):
            return self.stringify(value.struct)
        elif isinstance(value, (list, tuple)):
            if len(value) == 0 and not self.flat:
                return ""
            elif len(value) == 1 and not self.flat:
                return self.stringify(value[0])
            else:
                return "[%s]" % (", ".join(map(self.stringify, value)))
        elif isinstance(value, PrimaryObject):
            return value
        else:
            return str(value)

    def clean(self, values, names):
        """
        Given the values and names of columns, change the values
        into string versions for the display table.
        """
        if self.raw:
            return values
        retval = []
        for i in range(len(values)):
            if names[i].endswith("handle"):
                retval.append(repr(values[i].struct["handle"]))
            else:
                retval.append(self.stringify(values[i]))
        return retval

    def do_query(self, items, table):
        """
        Perform the query on the items in the named table.
        """
        # table: a class that has .row(1, 2, 3, ...)
        with self.database.get_transaction_class()("QueryQuickview",
                                                   self.database,
                                                   batch=True) as trans:
            ROWNUM = 0
            env = self.make_env()
            for item in items:
                if item is None:
                    continue
                row = []
                row_env = []
                # "col[0]" in WHERE clause will return first column of selection:
                env["col"] = row_env
                env["ROWNUM"] = ROWNUM
                env["object"] = item
                struct = Struct(item.to_struct(), self.database)
                env.set_struct(struct)
                for col in self.columns:
                    try:
                        value = eval(col, env)
                    except:
                        value = None
                    row.append(value)
                    # allow col[#] reference:
                    row_env.append(value)
                    # an alias?
                    if col in self.aliases:
                        env[self.aliases[col]] = value
                # Should we include this row?
                if self.where:
                    try:
                        result = eval(self.where, env)
                    except:
                        continue
                else:
                    if self.action in ["DELETE", "UPDATE"]:
                        result = True
                    else:
                        result = any([col != None
                                      for col in row])  # are they all None?
                # If result, then append the row
                if result:
                    if (self.limit is None) or (self.limit[0] <= ROWNUM <
                                                self.limit[1]):
                        if self.action == "SELECT":
                            if not self.flat:
                                # Join by rows:
                                products = []
                                columns = []
                                count = 0
                                for col in row:
                                    if ((isinstance(col, Struct)
                                         and isinstance(col.struct, list)
                                         and len(col.struct) > 0)
                                            or (isinstance(col, list)
                                                and len(col) > 0)):
                                        products.append(col)
                                        columns.append(count)
                                    count += 1
                                if len(products) > 0:
                                    current = self.clean(row, self.columns)
                                    for items in itertools.product(*products):
                                        for i in range(len(items)):
                                            current[
                                                columns[i]] = self.stringify(
                                                    items[i])
                                        table.row(
                                            *current,
                                            link=(item.__class__.__name__,
                                                  item.handle))
                                        self.select += 1
                                else:
                                    table.row(*self.clean(row, self.columns),
                                              link=(item.__class__.__name__,
                                                    item.handle))
                                    self.select += 1
                            else:
                                table.row(*self.clean(row, self.columns),
                                          link=(item.__class__.__name__,
                                                item.handle))
                                self.select += 1
                        elif self.action == "UPDATE":
                            # update table set col=val, col=val where expr;
                            table.row(*self.clean(row, self.columns),
                                      link=(item.__class__.__name__,
                                            item.handle))
                            self.select += 1
                            for i in range(len(self.setcolumns)):
                                struct.setitem(self.setcolumns[i],
                                               eval(self.values[i], env),
                                               trans=trans)
                        elif self.action == "DELETE":
                            table.row(*self.clean(row, self.columns))
                            self.select += 1
                            self.database.remove_instance(item, trans)
                        else:
                            raise AttributeError("unknown command: '%s'",
                                                 self.action)
                    ROWNUM += 1
                    if (self.limit is not None) and (ROWNUM >= self.limit[1]):
                        break
Ejemplo n.º 4
0
class DBI(object):
    """
    The SQL-like interface to the database and document instances.
    """
    def __init__(self, database, document=None):
        self.database = database
        self.document = document
        self.data = {}
        self.select = 0
        self.flat = False
        self.raw = False
        if self.database:
            for name in self.database.get_table_names():
                d = self.database._tables[name]["class_func"]().to_struct()
                self.data[name.lower()] = d.keys()
        # The macros:
        self.shortcuts = {
            "SURNAME": "primary_name.surname_list[0].surname",
            "GIVEN": "primary_name.first_name",
        }

    def parse(self, query):
        """
        Parse the query.
        """
        self.query_text = query.replace("\n", " ").strip()
        lexed = self.lexer(self.query_text)
        #print(lexed)
        self.parser(lexed)
        for col_name in self.columns[:]: # copy
            if col_name == "*":
                self.columns.remove('*')
                # this is useful to see what _class it is:
                self.columns.extend(self.get_columns(self.table))
                # otherwise remove metadata:
                #self.columns.extend([col for col in self.get_columns(self.table) if not col.startswith("_"))

    def lexer(self, string):
        """
        Given a string, break into a list of chunks.
        """
        retval = []
        state = None
        current = ""
        stack = []
        i = 0
        # Handle macros:
        for key in self.shortcuts.keys():
            string = string.replace(key, self.shortcuts[key])
        # (some "expression" in ok)
        # func(some "expression" in (ok))
        # list[1][0]
        # [x for x in list]
        while i < len(string):
            ch = string[i]
            #print("lex:", i, ch, state, retval, current)
            if state == "in-double-quote":
                if ch == '"':
                    state = stack.pop()
                current += ch
            elif state == "in-single-quote":
                if ch == "'":
                    state = stack.pop()
                current += ch
            elif state == "in-expr":
                if ch == ")":
                    state = stack.pop()
                elif ch == "(":
                    stack.append("in-expr")
                current += ch
            elif state == "in-square-bracket":
                if ch == "]":
                    state = stack.pop()
                elif ch == "[":
                    stack.append("in-square-bracket")
                current += ch
            elif ch == '"':
                stack.append(state)
                state = "in-double-quote"
                current += ch
            elif ch == "'":
                stack.append(state)
                state = "in-single-quote"
                current += ch
            elif ch == "(":
                stack.append(state)
                state = "in-expr"
                current += "("
            elif ch == "[":
                stack.append(state)
                state = "in-square-bracket"
                current += "["
            elif ch == ",":
                if current:
                    retval.append(current)
                retval.append(",")
                current = ""
            elif ch == "=":
                if current:
                    retval.append(current)
                retval.append("=")
                current = ""
            elif ch in [' ', '\t', '\n', ";"]: # break
                if current:
                    retval.append(current)
                    if current.upper() == "WHERE":
                        # HACK: get rest of string:
                        if string[-1] == ";":
                            retval.append(string[i + 1:-1])
                            i = len(string) - 2
                        else:
                            retval.append(string[i + 1:])
                            i = len(string) - 1
                    current = ""
                else:
                    pass # ignore whitespace
            else:
                current += ch
            i += 1
        if current:
            retval.append(current)
        #print("lexed:", retval)
        return retval

    def parser(self, lex):
        """
        Takes output of lexer, and sets the values.
        After parser, the DBI will be ready to process query.
        """
        self.action = None
        self.table = None
        self.columns = []
        self.setcolumns = []
        self.values = []
        self.aliases = {}
        self.limit = None
        self.where = None
        self.index = 0
        while self.index < len(lex):
            symbol = lex[self.index]
            if symbol.upper() == "FROM":
                # from table select *;
                self.index += 1
                if self.index < len(lex):
                    self.table = lex[self.index]
            elif symbol.upper() == "SELECT":
                # select a, b from table;
                self.action = "SELECT"
                self.index += 1
                self.columns.append(lex[self.index])
                self.index += 1
                while self.index < len(lex) and lex[self.index].upper() in [",", "AS"]:
                    sep = lex[self.index]
                    if sep == ",":
                        self.index += 1
                        self.columns.append(lex[self.index])
                        self.index += 1
                    elif sep.upper() == "AS":
                        self.index += 1 # alias
                        self.aliases[self.columns[-1]] = lex[self.index]
                        self.index += 1
                self.index -= 1
            elif symbol.upper() == "DELETE":
                # delete from table where item == 1;
                self.action = "DELETE"
                self.columns = ["gramps_id"]
            elif symbol.upper() == "SET":
                # SET x=1, y=2
                self.index += 1
                self.setcolumns.append(lex[self.index]) # first column
                self.index += 1 # equal sign
                # =
                self.index += 1 # value
                self.values.append(lex[self.index])
                self.index += 1 # comma
                while self.index < len(lex) and lex[self.index] == ",":
                    self.index += 1 # next column
                    self.setcolumns.append(lex[self.index])
                    self.index += 1 # equal
                    # =
                    self.index += 1 # value
                    self.values.append(lex[self.index])
                    self.index += 1 # comma?
                self.index -= 1
            elif symbol.upper() == "LIMIT":
                self.index += 1 # after LIMIT
                number = lex[self.index]
                self.index += 1 # maybe a comma
                if self.index < len(lex) and lex[self.index] == ",":
                    self.index += 1 # after ","
                    stop = lex[self.index]
                    self.limit = (int(number), int(stop))
                else:
                    self.limit = (0, int(number))
                    self.index -= 1
            elif symbol.upper() == "WHERE":
                # how can we get all of Python expressions?
                # this assumes all by ;
                self.index += 1
                self.where = lex[self.index]
            elif symbol.upper() == "UPDATE":
                # update table set x=1, y=2 where condition;
                # UPDATE gramps_id set tag_list = Tag("Betty") from person where  "Betty" in primary_name.first_name
                self.columns = ["gramps_id"]
                self.action = "UPDATE"
                if self.index < len(lex):
                    self.index += 1
                    self.table = lex[self.index]
            elif symbol.upper() == "FLAT":
                self.flat = True
            elif symbol.upper() == "EXPAND":
                self.flat = False
            elif symbol.upper() == "RAW":
                self.raw = True
            elif symbol.upper() == "NORAW":
                self.raw = False
            else:
                raise AttributeError("invalid SQL expression: '... %s ...'" % symbol)
            self.index += 1

    def close(self):
        """
        Close up any progress widgets or dialogs.
        """
        #try:
        #    self.progress.close()
        #except:
        pass

    def clean_titles(self, columns):
        """
        Take the columns and turn them into strings for the table.
        """
        retval = []
        for column in columns:
            if column in self.aliases:
                column = self.aliases[column]
            retval.append(column.replace("_", "__"))
        return retval

    def query(self, query):
        self.parse(query)
        self.select = 0
        start_time = time.time()
        class Table():
            results = []
            def row(self, *args, **kwargs):
                self.results.append([args, kwargs])
            def get_rows(self):
                return [list(item[0]) for item in self.results]
        table = Table()
        self.sdb = SimpleAccess(self.database)
        self.process_table(table) # a class that has .row(1, 2, 3, ...)
        print(_("%d rows processed in %s seconds.\n") % (self.select, time.time() - start_time))
        return table

    def eval(self):
        """
        Execute the query.
        """
        self.sdb = SimpleAccess(self.database)
        self.stab = QuickTable(self.sdb)
        self.select = 0
        start_time = time.time()
        self.process_table(self.stab) # a class that has .row(1, 2, 3, ...)
        if self.select > 0:
            self.stab.columns(*self.clean_titles(self.columns))
            self.sdoc = SimpleDoc(self.document)
            self.sdoc.title(self.query_text)
            self.sdoc.paragraph("\n")
            self.sdoc.paragraph("%d rows processed in %s seconds.\n" % (self.select, time.time() - start_time))
            self.stab.write(self.sdoc)
            self.sdoc.paragraph("")
        return _("%d rows processed in %s seconds.\n") % (self.select, time.time() - start_time)

    def get_columns(self, table):
        """
        Get the columns for the given table.
        """
        if self.database:
            retval = self.data[table.lower()]
            return retval # [self.name] + retval
        else:
            return ["*"]

    def process_table(self, table):
        """
        Given a table name, process the query on the elements of the
        table.
        """
        # 'Person', 'Family', 'Source', 'Citation', 'Event', 'Media',
        # 'Place', 'Repository', 'Note', 'Tag'
        # table: a class that has .row(1, 2, 3, ...)
        if self.table == "person":
            self.do_query(self.sdb.all_people(), table)
        elif self.table == "family":
            self.do_query(self.sdb.all_families(), table)
        elif self.table == "event":
            self.do_query(self.sdb.all_events(), table)
        elif self.table == "source":
            self.do_query(self.sdb.all_sources(), table)
        elif self.table == "tag":
            self.do_query(self.sdb.all_tags(), table)
        elif self.table == "citation":
            self.do_query(self.sdb.all_citations(), table)
        elif self.table == "media":
            self.do_query(self.sdb.all_media(), table)
        elif self.table == "place":
            self.do_query(self.sdb.all_places(), table)
        elif self.table == "repository":
            self.do_query(self.sdb.all_repositories(), table)
        elif self.table == "note":
            self.do_query(self.sdb.all_notes(), table)
        else:
            raise AttributeError("no such table: '%s'" % self.table)

    def get_tag(self, name):
        tag = self.database.get_tag_from_name(name)
        if tag is None:
            tag = gramps.gen.lib.Tag()
            tag.set_name(name)
            trans_class = self.database.get_transaction_class()
            with trans_class("QueryQuickview new Tag", self.database, batch=False) as trans:
                self.database.add_tag(tag, trans)
        return Handle("Tag", tag.handle)

    def make_env(self, **kwargs):
        """
        An environment with which to eval elements.
        """
        retval= Environment({
            _("Date"): gramps.gen.lib.date.Date,
            _("Today"): gramps.gen.lib.date.Today(),
            "random": random,
            "re": re,
            "db": self.database,
            "sdb": self.sdb,
            "lib": gramps.gen.lib,
            "_": _,
            "Tag": self.get_tag,
            })
        retval.update(__builtins__)
        retval.update(kwargs)
        return retval

    def stringify(self, value):
        """
        Turn the value into an appropriate string representation.
        """
        if self.raw:
            return value
        if isinstance(value, Struct):
            return self.stringify(value.struct)
        elif isinstance(value, (list, tuple)):
            if len(value) == 0 and not self.flat:
                return ""
            elif len(value) == 1 and not self.flat:
                return self.stringify(value[0])
            else:
                return "[%s]" % (", ".join(map(self.stringify, value)))
        elif isinstance(value, PrimaryObject):
            return value
        else:
            return str(value)

    def clean(self, values, names):
        """
        Given the values and names of columns, change the values
        into string versions for the display table.
        """
        if self.raw:
            return values
        retval = []
        for i in range(len(values)):
            if names[i].endswith("handle"):
                retval.append(repr(values[i].struct["handle"]))
            else:
                retval.append(self.stringify(values[i]))
        return retval

    def do_query(self, items, table):
        """
        Perform the query on the items in the named table.
        """
        # table: a class that has .row(1, 2, 3, ...)
        with self.database.get_transaction_class()("QueryQuickview", self.database, batch=True) as trans:
            ROWNUM = 0
            env = self.make_env()
            for item in items:
                if item is None:
                    continue
                row = []
                row_env = []
                # "col[0]" in WHERE clause will return first column of selection:
                env["col"] = row_env
                env["ROWNUM"] = ROWNUM
                env["object"] = item
                struct = Struct(item.to_struct(), self.database)
                env.set_struct(struct)
                for col in self.columns:
                    try:
                        value = eval(col, env)
                    except:
                        value = None
                    row.append(value)
                    # allow col[#] reference:
                    row_env.append(value)
                    # an alias?
                    if col in self.aliases:
                        env[self.aliases[col]] = value
                # Should we include this row?
                if self.where:
                    try:
                        result = eval(self.where, env)
                    except:
                        continue
                else:
                    if self.action in ["DELETE", "UPDATE"]:
                        result = True
                    else:
                        result = any([col != None for col in row]) # are they all None?
                # If result, then append the row
                if result:
                    if (self.limit is None) or (self.limit[0] <= ROWNUM < self.limit[1]):
                        if self.action == "SELECT":
                            if not self.flat:
                                # Join by rows:
                                products = []
                                columns = []
                                count = 0
                                for col in row:
                                    if ((isinstance(col, Struct) and isinstance(col.struct, list) and len(col.struct) > 0) or
                                        (isinstance(col, list) and len(col) > 0)):
                                        products.append(col)
                                        columns.append(count)
                                    count += 1
                                if len(products) > 0:
                                    current = self.clean(row, self.columns)
                                    for items in itertools.product(*products):
                                        for i in range(len(items)):
                                            current[columns[i]] = self.stringify(items[i])
                                        table.row(*current, link=(item.__class__.__name__, item.handle))
                                        self.select += 1
                                else:
                                    table.row(*self.clean(row, self.columns), link=(item.__class__.__name__, item.handle))
                                    self.select += 1
                            else:
                                table.row(*self.clean(row, self.columns), link=(item.__class__.__name__, item.handle))
                                self.select += 1
                        elif self.action == "UPDATE":
                            # update table set col=val, col=val where expr;
                            table.row(*self.clean(row, self.columns), link=(item.__class__.__name__, item.handle))
                            self.select += 1
                            for i in range(len(self.setcolumns)):
                                struct.setitem(self.setcolumns[i], eval(self.values[i], env), trans=trans)
                        elif self.action == "DELETE":
                            table.row(*self.clean(row, self.columns))
                            self.select += 1
                            self.database.remove_instance(item, trans)
                        else:
                            raise AttributeError("unknown command: '%s'", self.action)
                    ROWNUM += 1
                    if (self.limit is not None) and (ROWNUM >= self.limit[1]):
                        break