|
@@ -370,33 +370,98 @@ Per-collection runtime settings. Currently supports timestamp precision; extensi
|
|
|
|
|
|
|
|
#### Timestamp precision
|
|
#### Timestamp precision
|
|
|
|
|
|
|
|
-Every document carries `_created_at` and `_updated_at` — int64 timestamps since epoch. By default these are **milliseconds**. Collections that receive rapid-fire writes (LLM streaming chunks, agent tool loops) often see ties at ms resolution that make `ORDER BY _created_at` ambiguous. Configure those collections for **nanoseconds** to get unique, strictly ordered timestamps.
|
|
|
|
|
|
|
+Every document carries `_created_at` and `_updated_at` — int64 timestamps since epoch.
|
|
|
|
|
+
|
|
|
|
|
+**Since 2.2.0 the default is nanoseconds (`ns`).** It was milliseconds up to
|
|
|
|
|
+2.1.x. The flip was made because collections that receive rapid-fire writes
|
|
|
|
|
+(LLM streaming chunks, agent tool loops) saw ties at ms resolution that made
|
|
|
|
|
+`ORDER BY _created_at` ambiguous; ns gives unique, strictly ordered timestamps
|
|
|
|
|
+out of the box. Collections that already had a stored config keep their value —
|
|
|
|
|
+only collections created after the upgrade *without* an explicit
|
|
|
|
|
+`configureCollection()` inherit the new default.
|
|
|
|
|
|
|
|
```cpp
|
|
```cpp
|
|
|
using Cfg = smartbotic::database::Client::CollectionConfig;
|
|
using Cfg = smartbotic::database::Client::CollectionConfig;
|
|
|
|
|
|
|
|
-// Default: ms (no configuration needed)
|
|
|
|
|
-auto id = db.insert("logs", {{"msg", "..."}});
|
|
|
|
|
-// doc["_created_at"] is ~13 digits, milliseconds since epoch
|
|
|
|
|
|
|
+// Default since 2.2.0: ns (no configuration needed)
|
|
|
|
|
+auto id = db.insert("llm_tokens", {{"token", "hello"}});
|
|
|
|
|
+// doc["_created_at"] is ~19 digits, nanoseconds since epoch
|
|
|
|
|
|
|
|
-// Switch a collection to nanoseconds
|
|
|
|
|
-db.configureCollection("llm_tokens", Cfg{"ns"});
|
|
|
|
|
|
|
+// Opt a collection back to milliseconds
|
|
|
|
|
+db.configureCollection("logs", Cfg{"ms"});
|
|
|
|
|
|
|
|
-// Future inserts into llm_tokens get ns-precision timestamps
|
|
|
|
|
-auto id2 = db.insert("llm_tokens", {{"token", "hello"}});
|
|
|
|
|
-// doc["_created_at"] is ~19 digits, nanoseconds since epoch
|
|
|
|
|
|
|
+// Future inserts into logs get ms-precision timestamps
|
|
|
|
|
+auto id2 = db.insert("logs", {{"msg", "..."}});
|
|
|
|
|
+// doc["_created_at"] is ~13 digits, milliseconds since epoch
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+> **Note:** `Client::CollectionConfig::timestampPrecision` still defaults to
|
|
|
|
|
+> `"ms"` in the header, so `Cfg{}` sends an explicit `"ms"` rather than meaning
|
|
|
|
|
+> "server default". Pass the precision you want explicitly, or leave the string
|
|
|
|
|
+> empty to mean "leave unchanged" (2.4.5+, see partial updates below).
|
|
|
|
|
+
|
|
|
#### Reading the config
|
|
#### Reading the config
|
|
|
|
|
|
|
|
```cpp
|
|
```cpp
|
|
|
-auto cfg = db.getCollectionConfig("llm_tokens");
|
|
|
|
|
-std::cout << cfg.timestampPrecision << std::endl; // "ns"
|
|
|
|
|
|
|
+auto cfg = db.getCollectionConfig("logs");
|
|
|
|
|
+std::cout << cfg.timestampPrecision << std::endl; // "ms"
|
|
|
|
|
+
|
|
|
|
|
+bool explicit_set = db.hasCollectionConfig("logs"); // true
|
|
|
|
|
+bool default_only = db.hasCollectionConfig("llm_tokens"); // false (still ns default)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### Version history on/off (2.4.5+)
|
|
|
|
|
+
|
|
|
|
|
+Version history can be switched per collection, **on a collection that already
|
|
|
|
|
+exists and already holds data**:
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+Cfg off;
|
|
|
|
|
+off.timestampPrecision = ""; // empty = leave precision unchanged
|
|
|
|
|
+off.versioningEnabled = false; // stop recording new versions
|
|
|
|
|
+db.configureCollection("metrics_events", off);
|
|
|
|
|
+
|
|
|
|
|
+// ... later, turn it back on
|
|
|
|
|
+Cfg on;
|
|
|
|
|
+on.timestampPrecision = "";
|
|
|
|
|
+on.versioningEnabled = true;
|
|
|
|
|
+db.configureCollection("metrics_events", on);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Semantics:
|
|
|
|
|
+
|
|
|
|
|
+- **Reversible.** Disabling stops *new* versions being recorded. History already
|
|
|
|
|
+ on disk stays readable via `getVersionHistory()` / `getDocumentVersion()`, so
|
|
|
|
|
+ re-enabling simply resumes appending.
|
|
|
|
|
+- **Not the same as `maxVersions = 0`,** which means "keep unlimited history".
|
|
|
|
|
+ `versioningEnabled = false` means "keep none".
|
|
|
|
|
+- **Durable.** The setting lives in the `_collection_meta` system collection, so
|
|
|
|
|
+ it survives restarts through the normal WAL + snapshot path.
|
|
|
|
|
+- Takes effect on the next write.
|
|
|
|
|
|
|
|
-bool explicit_set = db.hasCollectionConfig("llm_tokens"); // true
|
|
|
|
|
-bool default_only = db.hasCollectionConfig("logs"); // false (still ms default)
|
|
|
|
|
|
|
+#### Partial updates (2.4.5+)
|
|
|
|
|
+
|
|
|
|
|
+`configureCollection()` is a **partial update**. Fields you leave unset are
|
|
|
|
|
+preserved, not reset:
|
|
|
|
|
+
|
|
|
|
|
+- empty `timestampPrecision` → precision unchanged
|
|
|
|
|
+- unset `versioningEnabled` (`std::nullopt`) → versioning unchanged
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+Cfg justPrecision;
|
|
|
|
|
+justPrecision.timestampPrecision = "ms"; // versioningEnabled stays nullopt
|
|
|
|
|
+db.configureCollection("logs", justPrecision);
|
|
|
|
|
+// -> precision becomes ms; versioning is left exactly as it was
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+On `getCollectionConfig()`, `versioningEnabled` is always populated with the
|
|
|
|
|
+collection's effective value rather than the "leave unchanged" sentinel.
|
|
|
|
|
+
|
|
|
|
|
+> Before 2.4.5 this RPC replaced the whole config. That was harmless while
|
|
|
|
|
+> precision was the only field, but it is why `versioning_enabled` is declared
|
|
|
|
|
+> `optional` in the proto: a plain proto3 bool defaults to `false`, so any
|
|
|
|
|
+> precision-only call would otherwise have silently switched versioning **off**.
|
|
|
|
|
+
|
|
|
#### What configureCollection does NOT do
|
|
#### What configureCollection does NOT do
|
|
|
|
|
|
|
|
**It does not convert existing timestamps.** Flipping a collection from `ms` to `ns` only affects *future* inserts. Pre-existing docs keep whatever precision they had. This keeps the config flip atomic and reversible.
|
|
**It does not convert existing timestamps.** Flipping a collection from `ms` to `ns` only affects *future* inserts. Pre-existing docs keep whatever precision they had. This keeps the config flip atomic and reversible.
|
|
@@ -690,6 +755,52 @@ if (info) {
|
|
|
db.dropCollection("old_data");
|
|
db.dropCollection("old_data");
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+#### Collections and projects (2.4.5+)
|
|
|
|
|
+
|
|
|
|
|
+Collection names are namespaced with the client's `Config::project`, exactly
|
|
|
|
|
+like documents. You keep passing bare names; the client qualifies them on the
|
|
|
|
|
+wire and un-qualifies them coming back, so `listCollections()` round-trips:
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+Client::Config cfg;
|
|
|
|
|
+cfg.project = "acme";
|
|
|
|
|
+Client db(cfg);
|
|
|
|
|
+
|
|
|
|
|
+db.createCollection("users"); // -> acme:users
|
|
|
|
|
+db.insert("users", {{"name", "Ada"}}); // -> acme:users
|
|
|
|
|
+auto info = db.getCollectionInfo("users"); // -> acme:users
|
|
|
|
|
+db.listCollections(); // -> { "users", ... }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **Fixed in 2.4.5 — upgrade if you call `createCollection`.** On 2.3.0-2.4.4
|
|
|
|
|
+> `createCollection`, `dropCollection` and `getCollectionInfo` sent the **bare**
|
|
|
|
|
+> name while `insert`/`get`/`find` sent the project-qualified one. Because the
|
|
|
|
|
+> client qualifies against `Config::project` unconditionally — including
|
|
|
|
|
+> `default` — **every** project was affected. One `createCollection("things")`
|
|
|
|
|
+> plus one `insert("things")` created *two* collections: the bare one received
|
|
|
|
|
+> the options and stayed empty, while `<project>:things` was created implicitly
|
|
|
|
|
+> by the insert with **defaults**. No error was raised.
|
|
|
|
|
+>
|
|
|
|
|
+> Concretely, on affected versions:
|
|
|
|
|
+> - `vectorDimension` never reached the collection holding the data, so
|
|
|
|
|
+> `similaritySearch()` returned no hits.
|
|
|
|
|
+> - `encrypted = true` silently did not apply.
|
|
|
|
|
+> - `dropCollection()` returned `true` while every document remained readable —
|
|
|
|
|
+> it dropped the empty phantom.
|
|
|
|
|
+>
|
|
|
|
|
+> **After upgrading:** leftover empty phantom collections are harmless litter,
|
|
|
|
|
+> but options that were swallowed do **not** retroactively apply.
|
|
|
|
|
+> `createCollection` on a now-correctly-resolved existing collection returns
|
|
|
|
|
+> `false` and only updates `maxVersions`. Since `vectorDimension` is **immutable
|
|
|
|
|
+> after creation**, a collection that needs vector search must be recreated:
|
|
|
|
|
+> create a new collection with the right `vectorDimension`, copy the documents
|
|
|
|
|
+> across, then drop the old one.
|
|
|
|
|
+
|
|
|
|
|
+Note that `listCollections()` currently returns every project's collections —
|
|
|
|
|
+names outside your own project stay visibly qualified rather than being
|
|
|
|
|
+flattened into yours. Unlike `ListViewsRequest`, `ListCollectionsRequest` has no
|
|
|
|
|
+project filter yet.
|
|
|
|
|
+
|
|
|
### Vector Similarity Search
|
|
### Vector Similarity Search
|
|
|
|
|
|
|
|
Requires a collection created with `vectorDimension > 0`.
|
|
Requires a collection created with `vectorDimension > 0`.
|