Răsfoiți Sursa

docs(views): document views feature with usage + composition rules

fszontagh 3 luni în urmă
părinte
comite
9f0512c9e8
2 a modificat fișierele cu 93 adăugiri și 0 ștergeri
  1. 1 0
      CLAUDE.md
  2. 92 0
      docs/integration-guide.md

+ 1 - 0
CLAUDE.md

@@ -31,6 +31,7 @@ cmake --build build -j$(nproc) && ./build/tests/test_vector_storage
 - gRPC API with replication, events, file storage, migrations
 - **Vector Storage** — Embedding vectors stored alongside documents with SIMD-accelerated (AVX2/SSE4.1) cosine similarity search via `SimilaritySearch` RPC. Vectors use `_vector` document field, stored in parallel float arrays, persisted through WAL (`VEC_PUT`/`VEC_DELETE`) and snapshots (v3 format). Collection `vector_dimension` option is immutable after creation.
 - **Atomic Partial Updates** — `PatchDocument` RPC merges fields into existing documents atomically (server-side, within collection lock). Client `patch()` method. `update()` uses automatic optimistic locking with retry. See `docs/integration-guide.md` for concurrency patterns.
+- **Views** — Read-only named projections over collections. Include/exclude field lists (dot-notation for nested paths), baked-in filters with all operators (EQ/NE/GT/GTE/LT/LTE/IN/CONTAINS/EXISTS/REGEX/SEARCH) AND-merged with caller filters, optional default sort that caller can override. Stored in `_views` system collection, created via `create_view` migration op or `createView` RPC. Writes on view names are rejected.
 
 ## Packaging
 

+ 92 - 0
docs/integration-guide.md

@@ -242,6 +242,98 @@ if (!db.updateIfVersion("users", "user-123", *doc, version)) {
 }
 ```
 
+### Views
+
+Views are named, read-only projections over collections. They filter which fields are returned and optionally enforce baked-in filters and default sort order. Views are ideal for:
+
+- **Schema evolution** — add fields to a collection without affecting consumers that query through a view
+- **Security boundary** — hide sensitive fields (passwords, tokens) and enforce row-level constraints (status=active, tenant=X)
+- **API clarity** — name and document a stable contract ("this view returns these fields with these filters")
+
+#### Creating a view
+
+```cpp
+using Op = smartbotic::database::Client::FilterOp;
+
+// Simple: just project fields
+db.createView("users_public",
+              /*collection=*/"users",
+              /*include=*/{"id", "name", "avatar_url", "profile.public_bio"});
+
+// Full-featured: fields + filters + default sort
+db.createView("active_admins",
+              /*collection=*/"users",
+              /*include=*/{"id", "name", "role"},
+              /*exclude=*/{},
+              /*where=*/{
+                  {"status", Op::EQ, "active"},
+                  {"role",   Op::EQ, "admin"}
+              },
+              /*defaultSort=*/smartbotic::database::Client::Sort{"name", false});
+```
+
+#### Querying a view
+
+Views work like collections — use the view name in `get()`, `find()`, `count()`:
+
+```cpp
+auto user = db.get("users_public", "u1");         // projection applied
+auto results = db.find("active_admins", {
+    .filters = {{"department", Op::EQ, "eng"}},   // AND-merged with view's where
+    .limit = 50
+});
+auto total = db.count("active_admins", {{"department", Op::EQ, "eng"}});
+```
+
+#### Inspecting and dropping
+
+```cpp
+auto views = db.listViews();
+auto info = db.getViewInfo("users_public");
+db.dropView("users_public");
+```
+
+#### Semantics
+
+| Dimension | Behavior |
+|-----------|----------|
+| **Filters** | **View AND caller** — view's `where` is always applied. Caller's filters are merged with AND. Consumers cannot escape the view's constraints. |
+| **Sort** | **Caller overrides view** — if the caller specifies a sort, it wins. Otherwise the view's `defaultSort` is used. |
+| **Include** | Whitelist — when non-empty, only those paths are returned. |
+| **Exclude** | Blacklist — ignored if include is non-empty; otherwise removes listed paths. |
+| **Metadata** | `_id`, `_version`, `_created_at`, `_updated_at`, `_created_by`, `_updated_by` are always returned regardless of include/exclude. |
+| **Nested paths** | Dot-notation supported: `user.profile.name`. Stops at arrays. |
+| **Writes** | Rejected — `insert()`, `update()`, `patch()`, `upsert()`, `remove()` on a view name return an error. |
+| **View-of-view** | Not allowed — views must target real collections. |
+
+#### Via migrations (recommended)
+
+```json
+{
+  "version": "020",
+  "name": "create_active_admins_view",
+  "operations": [
+    {
+      "type": "create_view",
+      "name": "active_admins",
+      "collection": "users",
+      "include": ["id", "name", "role"],
+      "where": [
+        {"field": "status", "op": "EQ", "value": "active"},
+        {"field": "role",   "op": "EQ", "value": "admin"}
+      ],
+      "default_sort": {"field": "name", "descending": false}
+    }
+  ]
+}
+```
+
+The `op` field in `where` filters accepts either a string name (`EQ`, `NE`, `GT`, `GTE`, `LT`, `LTE`, `IN`, `CONTAINS`, `EXISTS`, `REGEX`, `SEARCH`) or the matching integer. Migrations are idempotent — re-running a migration that creates an existing view is a no-op.
+
+#### Why AND-merge filters?
+
+The view's filters act as a **security enforcement boundary**. A consumer querying `active_admins` literally cannot see inactive users — the filter is applied server-side, after authentication, before projection. This is how SQL views with `WHERE` clauses work, and how row-level security works in most databases. Any consumer filter is ANDed on top, narrowing further.
+
 ### Querying
 
 ```cpp