Skip to content

Commit 3f802fc

Browse files
Domantasclaude
andcommitted
docs(database): the gateway's last Database wave
[4] Hub and Api database pages: empty filter needs allRecords (DATABASE-037), own-record rights may call the bulk methods, no $ operators in updates (035), broken record documents (036 with Index), soft-deleted records not found, change owner needs a project user (MEMBERSHIP-USERS-012), aggregate joins and joinedCollections, test aggregate rights, rename without renameUniqueName (SCHEMA-002), delete blocked by aggregates (SCHEMA-018), integrations and schema triggers per env, taxonomy dependencyRefs, term-read rights and TAXONOMIES-005/010/011, collection imports. Examples that used a $set update or snake_case request names are fixed. Task file updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent b84a2f5 commit 3f802fc

3 files changed

Lines changed: 230 additions & 4 deletions

File tree

‎docs/api/database.md‎

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ This is the **taxonomy** tree, not the term tree: nodes are taxonomies. Every `t
280280
**Goal:** same structure, but also pull each taxonomy's terms in the same call.
281281

282282
```python
283-
structure = norbix.api.database.find_taxonomy_tree(include_terms=True)
283+
structure = norbix.api.database.find_taxonomy_tree(includeTerms=True)
284284
```
285285

286286
```json
@@ -316,7 +316,7 @@ structure = norbix.api.database.find_taxonomy_tree(include_terms=True)
316316

317317
Now each taxonomy node's `terms` holds that taxonomy's full term tree (same shape as `find_term_tree`) — *Countries* carries its countries, *Cities* carries its cities.
318318

319-
> Every term-reading call also accepts an optional `database_integration_id` to target a non-default database.
319+
> Every term-reading call also accepts an optional `databaseIntegrationId` to target a non-default database.
320320
321321
---
322322

@@ -337,3 +337,70 @@ roots, and the terms of its child taxonomies nest under them.
337337
```python
338338
tree = norbix.api.database.find_merged_term_tree("services")
339339
```
340+
341+
---
342+
343+
## Changing many records
344+
345+
`update_many` and `delete_many` change every record that matches `filter`. An
346+
empty filter (`'{}'`) matches **every** record of the collection, so the
347+
gateway refuses it with `CM-ERRORS-DATABASE-037` unless you also send
348+
`allRecords=True`. For `update_many` a missing filter counts as `'{}'`.
349+
350+
```python
351+
norbix.api.database.update_many("products", filter='{"status": "draft"}', update='{"status": "live"}')
352+
norbix.api.database.delete_many("products", filter="{}", allRecords=True) # every record
353+
```
354+
355+
Pass the flag with its wire name, `allRecords` (the SDK sends keyword
356+
arguments as given).
357+
358+
- **Own-record rights are enough.** A caller with only `createAsUser`,
359+
`updateOwn` or `deleteOwn` may call `insert_many`, `update_many` and
360+
`delete_many`; the call changes only the caller's own records (before, it
361+
was refused with 403).
362+
- **No `$` operators in an update.** `update_one` / `update_many` take the new
363+
values as a plain document (`'{"price": 10}'`). `$inc`, `$set` and the like
364+
are refused with `CM-ERRORS-DATABASE-035`.
365+
- **A broken record document** on `insert_one`, `insert_many` or `replace_one`
366+
is refused with `CM-ERRORS-DATABASE-036` "Invalid record document" (before,
367+
`005`). For `insert_many`, `err.errors[0].context["Index"]` is the position
368+
of the bad document.
369+
- **Soft-deleted records are not found** by update, replace and change owner;
370+
a bulk update skips them.
371+
- **Change owner** (`change_responsibility`) refuses a new owner who is not a
372+
user of the project in the request environment, with
373+
`CM-ERRORS-MEMBERSHIP-USERS-012`.
374+
375+
```python
376+
from norbix_python import NorbixError
377+
378+
try:
379+
norbix.api.database.delete_many("products", filter="{}")
380+
except NorbixError as err:
381+
assert err.error_code == "CM-ERRORS-DATABASE-037"
382+
```
383+
384+
## Term reads: rights and errors
385+
386+
- Term reads by taxonomy name (`find_terms`, `find_terms_children`,
387+
`find_term_tree`, `find_merged_term_tree`) check `database:read` on
388+
`database:term:{taxonomy id}`. The merged tree checks it on every nested
389+
taxonomy too.
390+
- `find_terms` and `find_terms_children` refuse `$where`, `$function` and
391+
`$accumulator` in `filter` with `CM-ERRORS-DATABASE-031`.
392+
- `CM-ERRORS-TAXONOMIES-010`: the taxonomy name is unknown (the merged tree
393+
used to answer `-003`).
394+
- `CM-ERRORS-TAXONOMIES-011`: the term tree has more than 5000 terms (whole
395+
taxonomy, merged tree, `find_taxonomy_tree(includeTerms=True)`).
396+
- `CM-ERRORS-TAXONOMIES-005`: a taxonomy name longer than 40 characters.
397+
- `find_taxonomy_tree(includeTerms=True)` fails when reading the terms fails.
398+
Before, it returned the taxonomies without terms.
399+
400+
## Aggregates
401+
402+
`aggregate` and `execute_aggregate` refuse a pipeline that joins a collection
403+
the caller cannot read (`CM-ERRORS-DATABASE-033`), a name that is not a schema
404+
in the request environment (`032`), or a join into another database (`034`).
405+
`execute_aggregate` also needs `database:read` on the collection, and refuses
406+
a saved `$out` / `$merge` / JavaScript stage (`031`).

‎docs/hub/database.md‎

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ extended-JSON **strings**.
9191
products = norbix.hub.database.find_records("products", filter='{"status": "active"}', pageSize=20)
9292
one = norbix.hub.database.find_one_record("products", "rec_1")
9393
created = norbix.hub.database.insert_record("products", document='{"title": "Lamp", "price": 12}')
94-
norbix.hub.database.update_one_record("products", "rec_1", update='{"$set": {"price": 10}}')
94+
norbix.hub.database.update_one_record("products", "rec_1", update='{"price": 10}')
9595
norbix.hub.database.delete_record("products", "rec_1")
9696
total = norbix.hub.database.count_records("products", filter='{"status": "active"}')
9797
indexes = norbix.hub.database.get_collection_indexes("products")
@@ -111,6 +111,158 @@ update no longer matches.
111111
test_schemas = Norbix(project_id="...", api_key="...", env="TEST").hub.database.get_database_schemas()
112112
```
113113

114+
## Changing many records
115+
116+
`update_many_records` and `delete_many_records` change every record that
117+
matches `filter`. An empty filter (`'{}'`) matches **every** record of the
118+
collection, so the gateway refuses it with `CM-ERRORS-DATABASE-037` unless
119+
you also send `allRecords=True`. For `update_many_records` a missing filter
120+
counts as `'{}'`.
121+
122+
```python
123+
# Only the matching records:
124+
norbix.hub.database.update_many_records("products", filter='{"status": "draft"}', update='{"status": "live"}')
125+
# Every record — the flag says you mean it:
126+
norbix.hub.database.delete_many_records("products", filter="{}", allRecords=True)
127+
```
128+
129+
Pass the flag with its wire name, `allRecords` (the SDK sends keyword
130+
arguments as given; on `update_many_records` it goes in the JSON body, on
131+
`delete_many_records` in the query). The marketplace built-ins `db.update` and
132+
`db.delete` take the same flag as the `allRecords` argument.
133+
134+
Other rules for record writes:
135+
136+
- **Who may call the bulk methods.** A caller with only own-record rights
137+
(`createAsUser`, `updateOwn`, `deleteOwn`) may now call `insert_many_records`,
138+
`update_many_records` and `delete_many_records`. The call changes only the
139+
caller's own records (before, it was refused with 403).
140+
- **No `$` operators in an update.** `update_one_record` / `update_many_records`
141+
take the new values as a plain document. An update with `$inc`, `$set` and
142+
the like is refused with `CM-ERRORS-DATABASE-035` (before, MongoDB failed
143+
at run time).
144+
- **A broken record document** on insert one, insert many or replace is
145+
refused with `CM-ERRORS-DATABASE-036` "Invalid record document" (before,
146+
`005` "Invalid filter document"). For insert many,
147+
`err.errors[0].context["Index"]` is the position of the bad document.
148+
- **Soft-deleted records are not found.** Update, replace and change owner
149+
no longer match a soft-deleted record: it answers "not found", and a bulk
150+
update skips it.
151+
- **Change owner** (`change_record_responsibility`) refuses a new owner who is
152+
not a user of the project in the request environment, with
153+
`CM-ERRORS-MEMBERSHIP-USERS-012`.
154+
155+
## Aggregates
156+
157+
- `get_database_aggregate(id)` returns `joinedCollections`: the schemas the
158+
pipeline joins (`$lookup` / `$graphLookup` / `$unionWith`), next to the
159+
start `schemaId`. The aggregate list rows do not carry it.
160+
- `save_database_aggregate(...)` refuses `$out`, `$merge`, `$where`,
161+
`$function` and `$accumulator` (`CM-ERRORS-DATABASE-031`) and a join it
162+
cannot follow (`034`); a pipeline that does not parse is refused with
163+
`CM-ERRORS-AGGREGATES-002`.
164+
- A pipeline (run, test or saved-and-executed) that joins a collection the
165+
caller cannot read is refused with `033`; a join to a name that is not a
166+
schema in the request environment with `032`.
167+
- `test_database_aggregate(...)` needs `database:create` **or**
168+
`database:update` on `database:aggregate:{schemaId}`, plus read. A caller
169+
with read rights only gets 403.
170+
171+
## Schemas: rename and delete
172+
173+
- `rename_database_schema(id, title="...")` sends only the new title. The old
174+
`renameUniqueName` switch is gone: a name that another schema in the same
175+
environment already uses is always refused with `CM-ERRORS-SCHEMA-002`
176+
("Two schemas cannot share one collection"); the error context carries
177+
`SchemaName`. The schema name is the MongoDB collection name, so two schemas
178+
can never share it.
179+
- `delete_database_schema(id)` is also refused while a saved aggregate starts
180+
from the schema or joins it: `CM-ERRORS-SCHEMA-018`. The message names the
181+
aggregates; `err.errors[0].context` has `BlockerAggregateIds` and
182+
`BlockerAggregateNames`. (A schema that a trigger uses is refused with
183+
`CM-ERRORS-SCHEMA-017`, `BlockerTriggerIds`.)
184+
- `save_database_schema(viewId=...)` with an id that is not a schema of the
185+
request environment answers "schema not found".
186+
- `update_database_schema_list_settings(...)` refusals all use
187+
`CM-ERRORS-SCHEMA-034`; the context's `Rule` says which rule failed.
188+
- `apply_database_schema_bundle(...)`: a refused step keeps its own error code
189+
and context.
190+
191+
## Database integrations per environment
192+
193+
`get_database_integrations()` lists only the integrations of the client's
194+
environment (`PROD` when no `env` is set); each row has `env`. A saved
195+
integration has `isSystemOwned` (read only). Saving into an environment the
196+
project does not have is refused. An integration whose secret is empty in the
197+
request environment answers `CM-ERRORS-DATABASE-038`.
198+
199+
## Schema triggers per environment
200+
201+
Schema triggers are kept per environment. Every schema-trigger call works on
202+
the client's environment (the `norbix-env` header; `PROD` when none is set):
203+
204+
- `get_schema_triggers()` lists only that environment's triggers; each row
205+
has `env`.
206+
- `get_schema_trigger(id)` returns `env`, and `schemaId` is now the owning
207+
schema's id (`sch_…`). Before, it wrongly held the trigger's own id
208+
(`trg_…`).
209+
- `enable_schema_trigger`, `disable_schema_trigger` and `delete_schema_trigger`
210+
act on the copy in that environment. No copy there answers
211+
`CM-ERRORS-TRIGGERS-002` (not found).
212+
- `save_schema_trigger(id=..., schemaId=...)` with the id of a trigger that
213+
belongs to another schema answers `CM-ERRORS-TRIGGERS-002`.
214+
215+
```python
216+
test = Norbix(project_id="...", api_key="...", env="TEST")
217+
test.hub.database.disable_schema_trigger("trg_1") # the TEST copy only
218+
```
219+
220+
## Taxonomies
221+
222+
- `get_database_taxonomies()` rows have `dependencyRefs` in place of the old
223+
`dependencyNames`: one `{"id": ..., "name": ...}` pair per entry of
224+
`dependencies`, in the same order. A dependency that no longer exists keeps
225+
its place with `"name": None`.
226+
227+
```python
228+
for row in norbix.hub.database.get_database_taxonomies()["list"]["items"]:
229+
names = [ref["name"] or f"(deleted {ref['id']})" for ref in row.get("dependencyRefs") or []]
230+
```
231+
232+
- `save_database_taxonomy(viewId=...)` with the id of an existing taxonomy is
233+
an update and needs `database:update` on `database:taxonomy:{viewId}`.
234+
Without `viewId` (or with an unknown one) it is a create and needs
235+
`database:create`. An update replaces the whole taxonomy: send every field
236+
you want to keep.
237+
- Term reads by taxonomy name (term tree, merged tree) check
238+
`database:read` on `database:term:{taxonomy id}`; the merged tree checks it
239+
on every nested taxonomy too.
240+
- `get_database_taxonomy_tree(includeTerms=True)` now fails when reading the
241+
terms fails. Before, it returned the taxonomies without terms.
242+
- Errors on term reads: `CM-ERRORS-TAXONOMIES-010` the taxonomy name is
243+
unknown (the merged tree used to answer `-003`); `-011` the term tree has
244+
more than 5000 terms (whole taxonomy, merged tree, `includeTerms`); `-005`
245+
a taxonomy name longer than 40 characters.
246+
247+
## Collection imports
248+
249+
Import CSV rows into a collection: ask for an upload URL, upload the file,
250+
analyze it, then create the import and follow it.
251+
252+
```python
253+
target = norbix.hub.database.request_import_upload_url(fileName="products.csv")["result"]
254+
httpx.put(target["url"], content=csv_bytes, headers={"Content-Type": target["contentType"]})
255+
preview = norbix.hub.database.analyze_import_file(file=target["file"], hasHeader=True)
256+
created = norbix.hub.database.create_collection_import(file=target["file"], schemaId="sch_1", hasHeader=True)
257+
status = norbix.hub.database.get_collection_import(created["id"])
258+
imports = norbix.hub.database.get_collection_imports()
259+
norbix.hub.database.delete_collection_import(created["id"])
260+
```
261+
262+
The request fields are listed under `RequestImportUploadUrlRequest`,
263+
`AnalyzeImportFileRequest` and `CreateCollectionImport` in
264+
`references/hub_dtos.py`.
265+
114266
## Trees, list settings, embed and bundles
115267

116268
- `get_database_taxonomy_tree()` — the taxonomy structure as a tree.

‎docs/tasks/database-last-wave.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ given); other modules than Database.
3232
schema delete blockers, `joinedCollections`, rename, schema triggers per
3333
env, taxonomy `dependencyRefs`, term-read rights and new error codes — done
3434
5. Full suite (`uv run pytest`, `make typecheck`, `make lint`) green; ship
35-
with `nbx-ship --wait-release --cleanup` — doing
35+
with `nbx-ship --wait-release --cleanup` — done (993 passed, mypy and ruff
36+
clean)
3637

3738
## Changes
3839

@@ -57,6 +58,12 @@ given); other modules than Database.
5758
would go out as `all_records`, which the gateway does not read. The docs
5859
and tests use the wire name `allRecords=True`, the same way the rest of the
5960
SDK uses `pageSize`. Left as is (this is the SDK's documented rule).
61+
- `docs/api/database.md` showed `find_taxonomy_tree(include_terms=True)` and
62+
"an optional `database_integration_id`": by the rule above those go out
63+
under the snake_case name, which the gateway's request names
64+
(`includeTerms`, `databaseIntegrationId`) do not match. Fixed here (step 4).
65+
- `docs/hub/database.md` showed `update_one_record(..., update='{"$set": ...}')`,
66+
which the gateway now refuses (`CM-ERRORS-DATABASE-035`). Fixed here (step 4).
6067

6168
## Rejected / moved out
6269

0 commit comments

Comments
 (0)