Releases: bluedynamics/plone-pgcatalog
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
v1.0.0b54
Changed
-
Stop duplicating
path,path_parent, andpath_depthbetween typed columns andidxJSONB (#132, PR #135). These three fields now live exclusively in their typed columns onobject_state. Previously identical values were stored in both places, wasting ~10 % of JSONB storage and — more importantly — blocking the PG planner from collecting per-column selectivity statistics on path-subtree filters. Indexes (8) and extended statistics (3) on these fields have been migrated to reference the typed columns directly. CustomPATH-type indexes (e.g.tgpath) are unaffected and continue to store their data inidx.Migration: Schema changes are picked up automatically on startup via idempotent
DROP … IF EXISTS+CREATE … IF NOT EXISTS. To strip the obsolete keys from existing JSONB on large catalogs:from plone.pgcatalog.migrations.strip_path_keys import run run(conn, batch_size=5000)
Safe to run online, idempotent, batched, restores connection state on exit.
Full changelog: v1.0.0b53...v1.0.0b54
v1.0.0b53
Fixed
-
Migration install handler silently dropped every
DateRecurringIndex(DRI) — e.g.plone.app.event's /bda.aaf.site'sgeneral_start/general_end— when replacing a foreignportal_catalogwithPlonePGCatalogTool._snapshot_catalogcorrectly captured the storedattr_recurdef/attr_untilattributes, but_build_extrahad no DRI branch, so the restoredextranamespace lacked therecurdef/untilkeys thatDateRecurringIndex.__init__reads. The constructor raisedAttributeError, the outertry/exceptin_restore_from_snapshotswallowed it as a warning, and the index was never created — which meantextract_idxnever indexed those fields, the IndexRegistry had no entry for them, and every site-wide Collection filtering ongeneral_endreturned zero results.Added the DRI translation in
_build_extra, plus a roundtrip test that actually instantiatesDateRecurringIndexwith the built extra. Issue #126.Existing deployments that migrated on an affected build have to re-add the missing indexes manually (the upgrade can't recover them without the original catalog snapshot). See CHANGES.md for the recipe.
Added
- Slow-query suggestions now produce covering composite indexes for the common
portal_type + effectiveRange + sort_on=effectivepattern (issue #122). The suggestion engine splits the legacy_NON_IDX_FIELDSinto purpose-specific constants, expandseffectiveRangeto itseffectivedate contributor, and appends the query'ssort_onfield as a trailing btree composite column so the planner can skip the ORDER BY sort step.
v1.0.0b52
Fixed
-
CatalogStateProcessor._enqueue_tika_jobsindexed result rows by integer position (row[0],row[1]), but the request-scoped connection pool uses adict_rowfactory, so every content save that produced an unresolved blob ref raisedKeyError: 0duringtpc_vote(e.g. uploading a Dexterity Image). Switched to column-name access.Existing tests didn't catch this because the integration tests opened their cursor with
tuple_rowand the unit tests mockedfetchall()with tuple rows — both diverged from production. Tests updated to usedict_rowto match the real pool.
v1.0.0b51
Added
-
Multivariate PostgreSQL statistics for every default composite catalog index on
object_state, plus extras for published-content filters (type + effective,type + expires). Without these, PG's per-column histograms treat JSONB-expression conditions as independent and underestimate joint selectivity, so the planner picks composite-index scans + heap filters over thousands of tuples instead of doing Bitmap-AND with available GIN indexes.On a published-Event navigation query observed in production: 911 ms → sub-100 ms.
Stats kinds chosen per cardinality profile:
mcv + dependenciesfor low-cardinality pairs (type + state,parent + type, etc.)dependenciesonly for path-pairs (CMS paths are essentially unique per row;mcvwould be wasteful)
On existing installations a one-shot
ANALYZE object_stateruns on the first write transaction after upgrade so the new statistics take effect immediately rather than waiting for autovacuum. Idempotent viapg_stats_extskip check. Multi-pod safe.Issue #122 — first of three PRs (engine refactor + EXPLAIN-driven coverage to follow).
v1.0.0b50
Fixed
-
release_request_connectionnow issues an explicitconn.rollback()before returning the connection to the pool. Otherwise an implicit transaction opened by a priorSELECTon the pool fallback path stays alive, holding avirtualxidthat blocksCREATE INDEX CONCURRENTLY. Companion fix to bluedynamics/zodb-pgjsonb#58 (the storage-conn path). Closes #118. -
Suggested Indexes UI detects already-applied suggestions with mixed-case field names (e.g.
Language) by matching index names case-insensitively — PostgreSQL folds unquoted identifiers to lowercase. Also strengthens expression normalization (whitespace around->>, iterative paren collapse, WHERE-anchored extraction) so generated and PG-storedindexdefforms compare equal.apply_indexis now idempotent when a valid index with the same name already exists — returns success no-op instead of propagating theDuplicateTableerror. Closes #119. -
Tika enqueue: resolve Dexterity
NamedBlobFile/NamedBlobImagewrapper OIDs via a second-hop lookup throughobject_state, so the queue receives jobs for modern Dexterity File/Image content. Previously_enqueue_tika_jobs()only looked up the OIDs it found in the content's state — which are the wrapper OIDs, not the innerZODB.blob.BlobOIDs. The direct lookup returned zero rows and the enqueue silently skipped. Flat-state content (legacy/Archetypes-style, where the content state carries a directZODB.blob.Blob@ref) is unchanged. Closes #115. -
_handle_uuidnow accepts list/tuple queries (uses= ANY(...)), matching_handle_fieldsemantics. Previously a list query such ascatalog.searchResults(UID=['f852...'])was stringified asstr(['f852...'])→"['f852...']"and the JSONB->>comparison never matched, so@@getVocabulary?name=plone.app.vocabularies.Catalogwith aplone.app.querystring.operation.list.containscriterion on UID returned an empty vocabulary. -
catalog._catalog.getIndex(name)now returns aPGIndexwrapper with PG-backed_indexanduniqueValues(), same ascatalog.Indexes[name]. Previously it returned the raw ZCatalog index with empty BTrees, which broke:plone.app.vocabularies.KeywordsVocabulary(empty Subject/Tags dropdowns).Products.CMFPlone.browser.search.Search.types_list()(empty "Item type" filter in@@search).plone.app.event.setuphandlers(DateIndex detection).- Other Plone code paths that bypass
catalog.Indexes[name].
Special indexes registered with
idx_key=None(SearchableText, path, effectiveRange) are returned unwrapped so dedicated columns are used for them.
v1.0.0b49
Added
-
Log all catalog queries for debugging via
PGCATALOG_LOG_ALL_QUERIES=1(#109). Runtime-toggleable, logs duration + SQL + params + keys at INFO level. Parameter repr truncated at 2000 chars to bound log size. See how-to/debug-queries for the production-safety note about PII in query params. -
Slow-query log format changed slightly: prefix is now
Slow SQL catalog query (%.2f ms)instead ofSlow catalog query (%.1f ms). Log-aggregation grep patterns may need an update.
Fixed
-
clearFindAndRebuild works on fresh installs (#112). The rebuild now walks
ISiteRootbreadth-first instead of relying on a PG path snapshot, so bootstrapping plone-pgcatalog on an existing ZODB correctly re-indexes all content. Memory-flat (path-string queue, no acquisition chains). Includes discussion items viaIConversationadapter whenplone.app.discussionis installed. -
Boolean query values are stringified to JSON notation
'true'/'false'so queries against JSONB->>comparisons match (#110). Previouslystr(True)produced'True'which never matched JSONB's lowercase form. Fix applied in query.py, pgindex.py, eeafacetednavigation.py, and backends.py. -
Index suggestion DDL now uses double parentheses around boolean casts:
((idx->>'field')::boolean)— single parens were a PG syntax error, breaking the ZMI "Apply" button for boolean suggestions (#106). -
Graceful fallback in
_load_idx_batch()when themetacolumn is missing (#105). PreventsUndefinedColumncrash on first read after upgrade. Root cause fix in zodb-pgjsonb 1.10.4. -
ZMI error display:
manage_apply_index/manage_drop_indexnow show errors in red (Bootstrapalert-danger) instead of green (#104).
v1.0.0b48
Fixed
- Fix startup warnings "security declaration for nonexistent method" for
unsupported ZCatalog stubs (getAllBrains,searchAll, etc.).
Changed
- Extract
@meta,object_provides, andallowedRolesAndUsersfrom
idxJSONB into dedicated columns via genericExtraIdxColumnmechanism.
Reducesidxsize by ~85% (from ~3.2 KB to ~400 B avg, below TOAST
threshold). Runclear_and_rebuildafter upgrading. (#98) object_providesqueries now use a dedicatedTEXT[]column with GIN
index instead of JSONB containment.- Removed
_backfill_allowed_rolesstartup function (superseded by
generic extraction mechanism).
v1.0.0b47
v1.0.0b46
Fixed
- Query cache: use catalog-specific change counter instead of
MAX(tid)(#94). The cache was invalidated on every ZODB write (~2500/hour from ScalesDict alone), making it nearly useless (~28% hit rate). Now usespgcatalog_change_seqwhich only increments on actual catalog writes. Expected hit rate 90%+ on typical sites.