Resolve relations to brains without waking objects¶
Problem¶
You hold a set of ZODB object ids — typically from zc.relation /
zope.intid (a RelationList field, or back-references from
catalog.findRelations(...)) — and you want the catalog brains for
those objects: their metadata, filtered by the current user’s View
permission.
The naive approach wakes every object:
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
intids = getUtility(IIntIds)
objs = [intids.queryObject(intid) for intid in relation_intids] # loads each!
On a listing/aside tile this is an object-load N+1: each queryObject
loads the object (and, for an anonymous request, walks the acquisition
chain to check View). A page with a few dozen related items can cost
hundreds of ZODB loads.
Solution¶
pgcatalog stores every record in object_state keyed by its zoid (the
BIGINT primary key — the catalog rid is the zoid). The built-in
zoid / oid query operator lets you fetch the security-filtered brains
for a set of object ids directly, without waking the objects:
brains = catalog(zoid=[123, 456, 789])
Getting the zoids from relations is wake-free — the intid utility’s
KeyReferenceToPersistent already stores the object’s oid, and
oid → zoid is just int.from_bytes(...):
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
intids = getUtility(IIntIds)
refs = intids.refs # IOBTree: intid -> KeyReferenceToPersistent
zoids = []
for intid in relation_intids:
kr = refs.get(intid)
if kr is not None:
zoids.append(int.from_bytes(kr.oid, "big")) # no getObject()
brains = catalog(zoid=zoids) # security-filtered, no wakes
If you happen to hold raw 8-byte ZODB oids instead of ints, pass them to
oid= and pgcatalog converts them for you:
brains = catalog(oid=[obj._p_oid for obj in ...])
Notes¶
Security.
zoid/oidcompose with the normal query and with theallowedRolesAndUsersfilter thatsearchResultsinjects, socatalog(zoid=...)returns only brains the current user may view. UseunrestrictedSearchResults(zoid=...)to bypass the filter.No storage, no reindex. The operator filters the existing primary-key column; there is nothing to index and no migration for existing sites.
Empty set matches nothing.
catalog(zoid=[])returns no rows (it never degrades to “match the whole table”).Accepted values.
zoidtakes ints or digit strings;oidtakes raw 8-byte oids; both accept a scalar or a list. Non-coercible values are dropped (ZCatalog-lenient).Composes.
catalog(zoid=[...], portal_type="Document")ANDs the two filters like any other query.