Releases: bluedynamics/plone-pgcatalog
v1.0.0b64
Fixed
_process_sortnow emitsidx->'{key}'(JSONB operator) instead ofidx->>'{key}'(text operator) for theFieldIndexfallback. Text-cast sorting compared everything lexicographically, so aFieldIndexover a numeric attribute ranked"10"before"2". JSONB comparison is type-aware: numbers sort numerically, strings lexically, so a homogeneousFieldIndexnow always sorts correctly regardless of value type. Affects anyFieldIndexwith numeric source data (counters, priorities, prices, weights).
v1.0.0b63
Changed
-
PGCatalogBrainis now catalog-independent._catalogdropped from__slots__;getURLuseszope.globalrequest.getRequest()only;getObject/_unrestrictedGetObjectresolve the traversal root lazily via a new_traversal_root()helper (getSite().getPhysicalRoot()first,getRequest().PARENTS[-1]as fallback). The catalog reference that ZODB prefetch needs has moved onto the transientCatalogSearchResultscontainer. This keeps brains cacheable / pickleable / re-queueable without dragging the Acquisition chain along. -
The
catalog=keyword onPGCatalogBrain.__init__is accepted but ignored — kept until the next major version for signature compatibility.
See #156.
v1.0.0b62
Fixed
-
_process_indexnow falls back to the correct handler for built-in
Plone indexes when they are missing from theIndexRegistry—
previously the miss fell through to_handle_fieldwhich emits
idx->>'name', bypassing the dedicated typed columns and their
indexes. On aaf-6 prod this produced 4-9 second seq-scans over a
450k-rowobject_statetable for every folder-listing query,
saturating the worker pool and causing intermittent Varnish
backend-fetch errors. Resolution happens via a new
_builtin_index_type(name)helper that combines two sources: the
three Plone-native specials (path,effectiveRange,
SearchableText) hardcoded because their SQL lives inside the
handler, and everyTEXT[]-typedExtraIdxColumn(derived at
dispatch time — currentlyallowedRolesAndUsersand
object_provides, extensible via
register_extra_idx_column). The correct handler then uses
path/allowed_roles/object_provides/
searchable_textcolumns and the DateRangeIndex composite
clause. Explicit registry entries still win so addons can override
behavior. Closes #154. -
_handle_keywordno longer crashes withTypeError: 'DateTime' object is not iterablewhen a caller passes a non-str, non-iterable
value (e.g. a ZopeDateTimeor a Pythondatetime) as the
query for a KeywordIndex. The old coercion assumed the value was
either astror iterable; anything else (DateTime,int,
…) hitlist(value)and raised. Values are now coerced via
str()into a single-element list, matching the JSONB storage
shape (keyword arrays always contain strings). Closes #152.
v1.0.0b61
Fixed
-
_field_rangeon FieldIndex no longer silently returns zero rows
for numeric range queries. Two stacked bugs:[max, min]order
from the caller was not normalized (produced always-false SQL), and
idx->>'key'comparisons ran lexicographically on text — so
'46.1' <= '5.0' <= '49.0'included values outside the range.
_field_rangenow sorts min/max and castsidx->>'key'to
::numericwhen the range values areint/float. String
values (ISO dates etc.) keep text comparison. Affected aaf-6 prod
map-widget bbox filter viacollective.collectionfilter— closes
#150. -
_CatalogCompat.getIndexand_CatalogIndexesView.__getitem__no
longer silently fall back to the raw ZCatalog index when they
cannot find the catalog tool — that fallback returned empty BTrees
and masked #143 / #146 for weeks. A new private helper
_resolve_catalogtries three paths in order (__parent__→
Acquisition chain →zope.component.hooks.getSite().portal_catalog)
and raisesRuntimeErrorif all three fail. -
_CatalogCompat.indexesproperty now self-heals a missing
__parent__on first access viagetSite().portal_catalog.
First page render after deploy persists__parent__; no second
upgrade-step click required on sites where the #139 upgrade ran
before that fix landed. Zero-touch prod recovery.
Added
-
PGIndex._apply_index(request, resultset=None)— ZCatalog-
compatible low-level query entry point. Returns
(IITreeSet(zoids), (index_name,)). Reuses
_QueryBuilder._process_indexso every registered IndexType
(FIELD, KEYWORD, PATH, DATE, DATE_RANGE, UUID, TEXT, BOOLEAN, GOPIP)
plus everyIPGIndexTranslatorutility works for free. No
implicit security filtering — matches ZCatalog semantics; use
catalog(**query)for secured results. Emits a
DeprecationWarningonce per caller site. -
_PGIndexMapping.__getitem__/__len__— round out the
PG-backed mapping so Plone core callers
(plone.app.uuid.utils,plone.app.vocabularies.Keywords)
work againstcatalog._catalog.getIndex(name)._indexwithout
needingcatalog.Indexes[name]acquisition. -
_PGIndexMapping.items()/values()raise
NotImplementedErrorwith guidance pointing atuniqueValues,
_apply_index, andcatalog(**query)as alternatives. No
Plone-core caller uses them on a wrapped index; a concrete usecase
can land in a future issue with server-side-cursor streaming. -
PGIndex._indexproperty emits aDeprecationWarningon
access — signals callers that the BTree-shaped API is an
emulation and suggests the preferred pgcatalog-native
alternatives.
Closes #146.
v1.0.0b60
Fixed
_PGIndexMapping(backingPGIndex._index) is now iterable and
branches its SQL on the index type.plone.app.vocabularies.Keywords
iteratesindex._indexdirectly to populate the tag-autocomplete
widget in the Plone edit form — the previous mapping had no
__iter__and itskeys()coerced JSONB arrays to their text
representation. Net effect on b59: typing into theSchlagwort
field offered zero suggestions even when matching keywords existed.
keys()/__iter__now use the sameUNION ALLexpansion as
uniqueValuesfor KEYWORD, andget()usesidx->key @> to_jsonb(value::text)so membership checks against a keyword
actually match. Follow-up to #143.
v1.0.0b59
Fixed
-
PGIndex.uniqueValues()now branches on the wrapped index type.
ForIndexType.KEYWORDthe JSONB value is a list of tags, so the
SQL usesjsonb_array_elements_textto expand it into individual
entries. Previously all index types went throughidx->>key,
which coerces a JSONB array to its JSON text representation —
producing entries like'["Werkvortrag", "Tirol"]'instead of
'Werkvortrag'/'Tirol'. Callers (the querystring
composer vocabulary,plone.app.vocabularies.Keywords,
collective.collectionfiltertag clouds, etc.) now see the
distinct set of elements as expected.A defensive
UNION ALLbranch treats a scalar row under the
same keyword key (corrupt/legacy data) as a single-value keyword
so the query does not raisecannot extract elements from a scalar._maybe_wrap_indexpasses the registered
IndexTypethrough toPGIndexso callers that build the
wrapper directly keep getting the (correct) scalar path by
default.Closes #143.
v1.0.0b58
Fixed
-
Register
SanitizeRowsModifieras anIQueryModifierutility so
that malformed Collection-query rows (missing or non-stringi
field) are dropped beforeplone.app.querystring.parseFormquery
processes them. Upstreamqueryparser.py:73otherwise sets
query[None] = ...and the subsequentcatalog(**parsedquery)
inquerybuilder._makequeryfails at the Python level with
TypeError: keywords must be strings— which the Collection edit
widget cannot recover from. With the sanitizer in place the
preview renders again, so editors can open their Collections and
repair corruptedSubject/ tag data through the UI.This is a defensive workaround for an upstream gotcha, not a fix for
the underlyingplone.app.querystringbehavior. Closes #142.
v1.0.0b57
Fixed
PGCatalogBrain.getObject()now mirrors upstream
Products.ZCatalog.CatalogBrains.AbstractCatalogBrain.getObject:
the parent path is traversed unrestricted and only the final
object isrestrictedTraverse-checked. Previously the full
path went throughrestrictedTraverse, so any intermediate
container with stricter permissions than the leaf raised
AccessControl.unauthorized.Unauthorized— even though the
catalog filter had already authorized access to the target.
Sites with a private parent folder publishing individual public
items (the common "kalender/event-xyz" pattern) hit this on every
anonymous render. Closes #141.
v1.0.0b56
Fixed
-
The v1->v2 upgrade step was silently no-op on production sites. GenericSetup
invokes upgrade handlers with theportal_setuptool as the context, but
_resolve_compatonly understood theImportContextshape (getSite()),
so the setup-tool call path hit thereturn None, Nonebranch and logged
migrate_catalog_indexes: no _CatalogCompat found; skipping— while
GenericSetup happily bumped the profile version to 2. Net effect: the
persisted_CatalogCompatkept its legacyindexesattribute and the
newindexesproperty then raisedAttributeErrorfor_raw_indexes,
which Acquisition swallowed and replaced with the tool'sindexes()
method — surfacing as'function' object has no attribute 'keys'from
catalog.indexes.keys()and'method' object is not subscriptablefrom
catalog.indexes[name]._resolve_compatnow also walksaq_parent(context)to reach the Plone
site when the context is the setup tool, so the normal ZMI
manage_upgradespath migrates the state as intended. -
Made
_CatalogCompat.indexesself-healing: if the legacyindexes
attribute is still in__dict__(unmigrated or fresh-install site that
skipped v1), the property moves it to_raw_indexeson first access and
marks the instance dirty. This avoids the Acquisition-swallowed
AttributeErrorfailure mode even when the upgrade step never ran.Closes #139.
v1.0.0b55
Fixed
-
Wrap
portal_catalog._catalog.indexes[name]access (#137, PR #138). Plone and addon code commonly reaches into the catalog via the non-API-conform patterncat._catalog.indexes[name]/.get(name)/.items(). Previously this returned raw ZCatalog index objects with empty BTrees, so queries silently returned no results._CatalogCompat.indexesis now a property returning a transient view that wraps each index withPGIndex(same behavior ascatalog.Indexes[name]). CustomPATH-type indexes and other special indexes (idx_key=None) continue to be returned raw.Based on prior prototyping by @thet on the
thet/indexes-wrapperbranch.Migration: GenericSetup profile bumped from v1 to v2. The upgrade step renames the persisted
indexesattribute to_raw_indexesand sets__parent__on the compat soaq_parentcan reach the catalog tool. Run Plone Site Setup → Add-ons → plone.pgcatalog → Upgrade on existing sites, or let the nextrunAllImportStepson the default profile pick it up.Likely-affected callers include
plone.base.utils.check_id(reserved-name check),plone.restapi.search.query.Query.get_index,plone.app.discussion,plone.app.referenceablebehavior,plone.volto,collective.collectionfilter, andcollective.exportimport— per-package verification is recommended after upgrade.
Full changelog: v1.0.0b54...v1.0.0b55