IPGIndexTranslator interface reference¶
This page documents the IPGIndexTranslator interface for extending
plone.pgcatalog with custom index types, its methods, security
contract, registration, wiring points, and built-in implementations.
Interface definition¶
Defined in plone.pgcatalog.interfaces:
class IPGIndexTranslator(Interface):
"""Named utility that translates a custom index's data for PG storage + querying."""
def extract(obj, index_name):
"""Extract value(s) from obj for this index.
Returns dict to merge into idx JSONB."""
def query(index_name, query_value, query_options):
"""Translate ZCatalog query to SQL fragment + params.
Returns (sql_fragment, params_dict)."""
def sort(index_name):
"""Return SQL expression for ORDER BY, or None."""
Methods¶
extract(obj, index_name) -> dictCalled during indexing (from
catalog.py’s_extract_from_translators()). Theobjargument is the IIndexableObject-wrapped Plone content object. Returns a dict of key-value pairs to merge into theidxJSONB column. Return{}if this translator stores no data (for example, DateRangeInRangeIndex delegates to underlying indexes).query(index_name, raw, spec) -> tuple[str, dict]Called during query execution (from
query.py’s_process_index()).rawis the original query value as passed to the catalog.specis the normalized query dict (simple values are wrapped as{"query:" value}). Returns a tuple of(sql_fragment, params_dict)where the SQL fragment is inserted into the WHERE clause and params are bound via psycopg parameterized queries using%(name)splaceholders.sort(index_name) -> str | NoneCalled during sort processing (from
query.py’s_process_sort()). Returns a SQL expression for ORDER BY, orNoneif the index does not support sorting.
Security contract¶
All IPGIndexTranslator implementations must follow these rules:
All user-supplied values must use
%(name)sparameter placeholders in the SQL fragment. Never string-format values into SQL.Index/column identifiers in the SQL fragment should be hardcoded constants or validated via
validate_identifier().validate_identifier(name)(fromplone.pgcatalog.columns) rejects any name not matching^[a-zA-Z_][a-zA-Z0-9_]*$.The
query()return value is appended directly to the WHERE clause. SQL injection through the fragment would bypass all parameterization.
Registration¶
Via ZCML (recommended for addons)¶
<utility
provides="plone.pgcatalog.interfaces.IPGIndexTranslator"
factory=".my_translator.MyTranslatorFactory"
name="my_index_name"
/>
Via Python (used at startup for autodiscovered indexes)¶
from zope.component import provideUtility
from plone.pgcatalog.interfaces import IPGIndexTranslator
provideUtility(translator_instance, IPGIndexTranslator, name="my_index")
The utility name must match the ZCatalog index name.
Wiring points¶
Module |
Function |
When Called |
|---|---|---|
|
|
During indexing – calls |
|
|
During query – falls back to translator |
|
|
During sort – falls back to translator |
The fallback path in _process_index(): if an index name is not found
in the IndexRegistry, query.py calls queryUtility(IPGIndexTranslator, name=index_name).
If a translator is found, its query() method is
called.
If no translator is found either, the index is treated as a
simple JSONB field query.
Built-in implementations¶
DateRecurringIndexTranslator¶
Defined in plone.pgcatalog.dri.
Handles
Products.DateRecurringIndex instances.
Constructor attributes:
Attribute |
Description |
|---|---|
|
Object attribute for the base date (equals the index name) |
|
Object attribute for the RRULE string |
|
Object attribute for the until date (rarely used) |
Storage:
Two keys in the idx JSONB column:
{index_name}: ISO 8601 base date string{index_name}_recurrence: RFC 5545 RRULE string (if recurring)
RRULE strings are validated against the pattern
^(RRULE:)?FREQ=(YEARLY|MONTHLY|...) and capped at 1,000 characters.
Query strategies:
Range |
Non-recurring |
Recurring |
|---|---|---|
|
|
|
|
|
|
|
|
|
exact |
|
|
Sort: pgcatalog_to_timestamptz(idx->>'{index_name}') (base date).
DateRangeInRangeIndexTranslator¶
Defined in plone.pgcatalog.addons_compat.driri.
Handles
Products.DateRangeInRangeIndex instances.
Constructor attributes:
Attribute |
Description |
|---|---|
|
Name of the underlying start date index |
|
Name of the underlying end date index |
Storage:
No-op. extract() returns {}.
The underlying DateIndex or
DateRecurringIndex translators handle extraction into idx JSONB.
Query:
The query dict uses start and end keys (not query/range):
catalog(event_dates={"start": DateTime("2025-03-01"), "end": DateTime("2025-03-31")})
Case |
SQL Logic |
|---|---|
Non-recurring |
|
Recurring |
|
Sort: pgcatalog_to_timestamptz(idx->>'{startindex}') (start date
of the underlying start index).
Autodiscovery¶
Both translators are autodiscovered and registered at startup by
_register_dri_translators() and _register_driri_translators() in
startup.py.
These functions iterate the ZCatalog indexes, identify
DateRecurringIndex and DateRangeInRangeIndex instances by
meta_type, and register the corresponding translator utility with
the index name.