Skip to content

Commit 00591e4

Browse files
jensensclaude
andcommitted
Update docs to reflect @meta metadata type preservation (#23)
Document the two-tier idx structure, @meta codec encoding, and brain attribute resolution order across architecture, query-api, schema, and zcatalog-compat reference pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 47474ab commit 00591e4

5 files changed

Lines changed: 57 additions & 12 deletions

File tree

CHANGES.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 1.0.0b13
4+
5+
### Fixed
6+
7+
- Preserve original Python types for metadata columns (e.g. `brain.effective`
8+
now returns a Zope `DateTime` object instead of an ISO string).
9+
Non-JSON-native metadata values (DateTime, datetime, date, etc.) are
10+
encoded via the Rust codec into `idx["@meta"]` at write time and restored
11+
on brain attribute access with per-brain caching. JSON-native values
12+
(str, int, float, bool, None) remain in top-level `idx` unchanged.
13+
Backward compatible — old data without `@meta` still works.
14+
Fixes #23.
15+
316
## 1.0.0b12
417

518
### Fixed
@@ -12,7 +25,7 @@
1225
the portal root before traversal (matching Plone's `CatalogTool`).
1326
Fixes #21.
1427

15-
## 1.0.0b11
28+
## 1.0.0b11
1629

1730
### Fixed
1831

@@ -23,12 +36,12 @@
2336
- Fix ZMI "Update Catalog" and "Clear and Rebuild" buttons returning 404.
2437
Added missing `manage_catalogReindex` and `manage_catalogRebuild` methods.
2538
Fixes #19.
26-
39+
2740
- Fix `clearFindAndRebuild` indexing non-content objects (e.g. `acl_users`).
2841
Now filters for contentish objects only (those with a `reindexObject` method),
2942
matching Plone's `CatalogTool` behavior.
3043
Fixes #20.
31-
44+
3245
### Changed
3346

3447
- `uniqueValuesFor(name)` is now a supported API (no longer deprecated).

docs/sources/explanation/architecture.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,10 @@ query, and returns lightweight `_PendingBrain` instances with just enough interf
205205
6. **Results are wrapped in `CatalogSearchResults` with `PGCatalogBrain` objects.**
206206
Each brain is a lightweight wrapper around a PG row dict. It implements
207207
`ICatalogBrain` for Plone compatibility and supports attribute access into the
208-
`idx` JSONB for catalog metadata.
208+
`idx` JSONB for catalog metadata. Non-JSON-native metadata (such as Zope
209+
`DateTime` objects) is stored under `idx["@meta"]` via the Rust codec and
210+
decoded on first access with per-brain caching (see {doc}`../reference/schema`
211+
for the `@meta` structure).
209212

210213
## Lazy loading
211214

@@ -309,9 +312,11 @@ with PG-backed implementations:
309312
mapping table.
310313

311314
- **Brain attribute resolution** distinguishes known from unknown fields. Known
312-
catalog fields (registered indexes or metadata) return `None` when absent from the
313-
`idx` JSONB -- matching ZCatalog's Missing Value behavior. Unknown fields raise
314-
`AttributeError`, which triggers the `getObject()` fallback in
315+
catalog fields (registered indexes or metadata) are resolved first from the
316+
`idx["@meta"]` dict (for non-JSON-native types like `DateTime`), then from the
317+
top-level `idx` JSONB. Fields missing from both return `None` -- matching
318+
ZCatalog's Missing Value behavior. Unknown fields raise `AttributeError`,
319+
which triggers the `getObject()` fallback in
315320
`CatalogContentListingObject.__getattr__()`.
316321

317322
- **Blocked methods**: ZCatalog methods that would return wrong/empty data

docs/sources/reference/query-api.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,16 @@ Lightweight result object backed by a PostgreSQL row. Implements
338338
All registered indexes and metadata columns are accessible as
339339
attributes (e.g., `brain.portal_type`, `brain.Title`, `brain.Subject`).
340340

341+
- Non-JSON-native metadata (Zope `DateTime`, `datetime`, `date`, etc.)
342+
is stored in `idx["@meta"]` via the Rust codec and decoded on first
343+
access with per-brain caching. This means `brain.effective` returns
344+
a `DateTime` object, not an ISO string. See {doc}`schema` for the
345+
`@meta` JSONB structure.
346+
- JSON-native metadata (str, int, float, bool, None, lists/dicts of
347+
these) is stored directly in the top-level `idx` JSONB.
341348
- For registered indexes/metadata: returns `None` if the field is
342-
missing from the `idx` JSONB (Missing Value behavior, matching
343-
ZCatalog).
349+
missing from both `@meta` and top-level `idx` (Missing Value
350+
behavior, matching ZCatalog).
344351
- For unknown attributes: raises `AttributeError`. This is intentional:
345352
`CatalogContentListingObject.__getattr__()` catches `AttributeError`
346353
and falls back to `getObject()`, loading the real content object.

docs/sources/reference/schema.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,16 @@ together in a single JSONB document. Example for a typical Plone Page:
118118

119119
Key conventions:
120120

121-
- Dates are stored as ISO 8601 strings with timezone offsets.
121+
- **Index values** (used for PG queries) are converted to JSON-safe types:
122+
dates become ISO 8601 strings, etc. These live at the top level of idx.
123+
- **Metadata values** that are JSON-native (str, int, float, bool, None,
124+
and lists/dicts of these) also live at the top level of idx.
125+
- **Non-JSON-native metadata** (Zope `DateTime`, stdlib `datetime`, `date`,
126+
etc.) is encoded via the Rust codec (`zodb-json-codec`) into a nested
127+
`"@meta"` key. This preserves original Python types so that
128+
`brain.effective` returns a `DateTime` object, not a string.
129+
The `@meta` dict uses codec type markers (e.g. `@dt`, `@cls`+`@s`) and
130+
is decoded once per brain on first access (cached thereafter).
122131
- Multi-value fields (e.g., `Subject`, `allowedRolesAndUsers`) are
123132
stored as JSON arrays.
124133
- Boolean fields are stored as JSON `true`/`false`.
@@ -127,6 +136,10 @@ Key conventions:
127136
- Path fields (`path`, `path_parent`, `path_depth`) are stored in
128137
both the dedicated table columns and the idx JSONB for unified
129138
path query support.
139+
- Fields that are both indexes and metadata (e.g. `effective`) appear
140+
in both places: top-level idx holds the converted ISO string (for
141+
PG queries), while `@meta` holds the original `DateTime` (for brain
142+
attribute access).
130143

131144
## SQL Functions
132145

docs/sources/reference/zcatalog-compat.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,18 @@ index._index.get("Document") # PG query returning matching ZOIDs
9292

9393
`PGCatalogBrain` attribute access follows these rules:
9494

95-
| Attribute Type | Behavior |
95+
| Attribute Type | Resolution Order |
9696
|---|---|
97-
| Known index or metadata (in `IndexRegistry`) | Returns value from `idx` JSONB, or `None` if missing |
97+
| Known index or metadata (in `IndexRegistry`) | 1. `idx["@meta"]` (codec-decoded, for non-JSON-native types like `DateTime`) → 2. top-level `idx` JSONB → 3. `None` if missing from both |
9898
| Unknown attribute | Raises `AttributeError` |
9999

100+
Non-JSON-native metadata values (Zope `DateTime`, `datetime`, `date`,
101+
etc.) are stored under `idx["@meta"]` at write time via the Rust codec
102+
(`pickle_to_dict`). On first access, the `@meta` dict is decoded via
103+
`dict_to_pickle` + `pickle.loads` and cached per brain for the lifetime
104+
of the result set. This ensures `brain.effective` returns a `DateTime`
105+
object, not an ISO string.
106+
100107
The `AttributeError` for unknown attributes is intentional:
101108
`CatalogContentListingObject.__getattr__()` catches it and falls back to
102109
`getObject()`, loading the real content object. Returning `None` instead

0 commit comments

Comments
 (0)