-
Notifications
You must be signed in to change notification settings - Fork 0
GOQL
Every list view in Gramps Connect — People, Families, Events, and so on — has a search box. By default, typing into it does a fast plain-text match against that view's handful of common fields (for People: Gramps ID and name; for Place: ID, title, and place name; and so on) — enough for most everyday searches, and the default because it's simpler than a query language.
Next to the box is a checkbox: "Use Gramps Object Query Language." Turn it on and the same box instead takes a full GOQL (gramps-object-query-language) condition — a small language built specifically for querying Gramps records, capable of things plain-text matching can't touch at all: reaching across relationships (a join, in database terms — "every family whose mother and father share a surname"), and even reaching backward across them ("every note nothing points to any more"). This page is for anyone who wants to write one of those conditions — no programming experience needed.
GOQL is also what powers Gramplets' filter()/db. get_number_of() calls and the where= argument to people()/
families()/etc. — the same syntax you'd type into the search box with
the checkbox on also works from inside an add-on's Python code. (db. get_number_of()/the older bare count() are Gramplet-API functions for
counting matching records, not GOQL's own any/len covered below —
different thing, same name.)
Don't want to write it by hand at all? The "Filters" button next to each list's title offers dozens of common rules as plain checkboxes — combinable with AND/OR, nestable, and negatable — and builds the exact same GOQL underneath, shown read-only so you can see what it wrote. See the Overview page's feature list for that. Its "+ New custom rule…" is the one spot inside that dialog that still takes a raw GOQL condition by hand, same syntax as this whole page — once saved and named, it's reused as an ordinary checkbox from then on, never retyped.
A query is always written in the context of a view — Person, Family, Event, and so on — and is just one condition:
surname == 'Smith'This reads as: when in the Person view, find records where the surname equals 'Smith'. The view you're searching in fixes which kind of record the condition applies to.
A few symbols come up over and over:
| Symbol | Means |
|---|---|
==, !=
|
is equal to / is not equal to |
<, <=, >, >=
|
is less than, at most, more than, at least (earlier/later, smaller/bigger) |
and, or, not
|
combine conditions, or flip one around |
in [ ... ] |
matches any one of a list of values |
'text' in field |
matches if field contains 'text' anywhere in it |
like(field, 'pattern') |
matches a text pattern, where % stands for "anything" |
regex(field, 'pattern') |
matches a regular expression, for those who already know them |
Text values go in single quotes ('Smith'); numbers don't (1968).
and requires both sides to be true; or needs only one. Both read
naturally: gender == Person.MALE and surname == 'Smith' finds every man
named Smith, while given_name == 'John' or surname == 'Doyle' finds
everyone named John or anyone at all named Doyle. Mixing the two works
the same as ordinary arithmetic, where multiplication happens before
addition — and is checked before or — but parentheses make the intent
clear regardless: (gender == Person.MALE and surname == 'Smith') or given_name == 'Mary' finds every male Smith, plus anyone named Mary of
any gender.
not flips a condition around, matching whenever the thing inside it
isn't true: not (surname == 'Smith') is everyone whose last name isn't
Smith. Comparisons can also be chained, the way real Python allows —
Date('Jan 1, 1900') < birth.date.sortval < Date('Jan 1, 1950') finds
everyone born strictly between those two dates, in one line instead of
two joined with and.
Beyond a plain ==, a few looser ways to match text cover most everyday
searches. given_name in ['John', 'Jane'] matches any name in the list —
add as many as you like. 'an' in given_name matches anywhere in the
name (Jane, Alexander, Susan, ...) with no wildcards needed. like(field, 'J%') matches a pattern where % stands for "anything," so like (given_name, 'J%') matches John, Jane, James, and so on. For anyone
already comfortable with regular expressions, regex(field, 'pattern')
is more powerful still — regex(surname, '^[SD]') finds every Smith and
Doyle in one go, something like(...) can't express without writing out
a separate condition for each starting letter.
A field doesn't have to live directly on the record you're searching.
birth and death reach from a person to their birth or death event, so
birth.date.sortval is the date of that event, and
birth.place.title follows it one step further, to that event's place.
The same idea works across other record types: father and mother
reach from a family to each parent's own Person record
(father.surname == 'Smith'), source reaches from a citation to what
it cites, and enclosed_by reaches from a place to the place that
encloses it (a city's county, say — and it chains with itself, so
enclosed_by.enclosed_by.title reaches two levels up).
Both sides of a comparison can be one of these paths, not just a fixed
value, which is what makes a query like "families where the mother and
father share a last name" possible: father.surname == mother.surname.
Chaining and comparing can combine freely — father.birth.date.sortval < Date('Jan 1, 1850') reaches two steps from a family (to the father, then
to his birth event) to find older generations without knowing who they
are ahead of time.
There's no limit to how many of these hops one path can cross, and each
hop can land on a different kind of record entirely. Starting in the
Family view, father.birth.place.title == 'Chicago, Cook, Illinois, USA'
reaches from the family to the father's own Person record, from there to
his birth Event, and from there to that event's Place — three hops
through three different kinds of record, ending at the place's name, all
in one line. The same chaining works from any view, in whatever
combination the relationships above allow.
A person has exactly one birth event, but any number of children, notes,
citations, or attached media — those need a different kind of check.
any(c.given_name == 'Steve' for c in children) matches a family if any
child satisfies the condition; leaving the condition out (any(n for n in notes)) just asks whether anything is attached at all, which is how not any(n for n in notes) finds people with no notes recorded. len(...) asks
"how many" instead of "at least one" — len([c for c in children]) > 2
finds families with more than two children, and len([c for c in children if c.gender == Person.MALE]) > 1 narrows that to counting only the sons.
A collection is as far as a single query can reach in that direction,
though. The chaining described above (father.birth.place.title) only
works through relationships that connect to exactly one record —
any/len can tell you whether something inside a collection
matches a condition, but the query can't then keep chaining past that
match to reach one of its own related records. A family's father has
his own parent family, for instance — but since a person can be recorded
as a child of more than one family, that link is a collection too, one
step further than a query can currently follow. So "every family whose
father and grandfather were born in the same county" isn't something
GOQL can express yet.
Every collection above reaches outward — a family's own children, a
person's own notes. backlinks is the one collection that reaches the
other way: does anything else in the tree point to this record?
It's available from every one of the ten record types, even ones with no
outward collections of their own (a Tag has no children or notes of its
own, but plenty of records can be tagged, so Tag's own backlinks
condition finds tags nothing uses):
not any(bl for bl in backlinks) # nothing references this record at all
any(bl._class == 'Person' for bl in backlinks) # at least one Person references it
any(bl for bl in backlinks) and len([bl for bl in backlinks]) > 1 # referenced by more than one thingThis is what makes a query like "every source nobody cites any more" or
"every place with no events recorded at it" possible — not any(bl for bl in backlinks), run in the Source or Place view.
_class is the one field a backlinks condition can test — the
referrer's own record type ('Person', 'Family', ...). Since a
backlink's referrer can be any of the ten record types at once, _class
can't be followed any deeper the way a normal relationship can — there's
no _class.primary_name — but it does accept in/not in against a list:
any(bl._class in ['Person', 'Family'] for bl in backlinks)
any(bl._class != 'Media' for bl in backlinks)len([bl for bl in backlinks]) works exactly like len(...) above.
Date('...') understands ordinary date text, so birth.date.sortval >= Date('Jan 1, 1968') finds everyone born on or after that day. One thing
worth knowing: sortval is always a single point in time, with no
"about," "before," or "estimated" qualifier attached — a birth date
entered as "before 1968" has the same sortval as plain "Jan 1, 1968,"
so a >= comparison would count that person as born on or after the
cutoff even though "before" means the opposite. When that distinction
matters, compare birth.date.modifier against a named constant instead,
such as Date.MOD_ABOUT.
Some fields, like a person's gender or a citation's confidence, compare
against a named constant rather than a raw number — gender == Person.MALE or confidence >= Citation.CONF_HIGH — pulled straight from
Gramps itself so they never drift out of sync with what Gramps actually
stores.
GOQL is a small, fixed set of building blocks, not a full programming language — anything outside the patterns above is rejected with an error rather than guessed at.
This page covers the everyday patterns; the search box's own "i" button,
next to each view's search field, lists the exact fields and
relationships available for that view. For the complete syntax
reference — every operator, relationship, collection, and edge case, plus
the reasoning behind each — see
where_expr.md
in the gramps-object-query-language
repository, the project GOQL is built on.
Gramps Connect is part of the family of Gramps-based software.
Using the app
- Overview
- Installing
- Deploying
- Messaging
- GOQL (advanced search)
- Gramplets & Add-on Store
- Data Model & Editing
- FAQ
Building & contributing