tricoredb 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -1
- data/README.md +201 -6
- data/lib/tricoredb/builders.rb +142 -0
- data/lib/tricoredb/client.rb +1307 -30
- data/lib/tricoredb/errors.rb +7 -3
- data/lib/tricoredb/response.rb +10 -5
- data/lib/tricoredb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35bb477aa16cb254d9745e4290a8ec5e052ba11e53dba60c8aa3b7a80aaff1a8
|
|
4
|
+
data.tar.gz: 3b6a18885b33a6625cb896a47856cf8def9c6c57b8c14cca85c282026ace41cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bb412d2aab2c127906911819e76d9d7703b1593a2d2cd01949be19f6c3534f1a5e440f907331ab60cec875aac4e955771018096d3d6a08b1faa3da71bcb856f
|
|
7
|
+
data.tar.gz: afc95e82a32c49fb6a5ca4dbeab10b8e42f93684842d1a42a8f3ee35b526746c3fba3654cb6152ea4715da9e9bcb20a221984ce6752601f2138c6f4f13c2f651
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,61 @@ All notable changes to the `tricoredb` gem are recorded here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-10-13
|
|
10
|
+
|
|
11
|
+
Every method added here needs a server at **0.1.0-rc.3 or later**. An older server
|
|
12
|
+
does not know the operation and answers it with an error.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- 116 methods, one for each operation the server gained since 0.1.0-rc.2. The README
|
|
17
|
+
lists every one beside the server operation it sends.
|
|
18
|
+
- Cache (53): sorted sets, locks with fencing tokens, bitmaps, HyperLogLog,
|
|
19
|
+
multi-key reads and writes, conditional and atomic read-and-change writes,
|
|
20
|
+
stale-while-revalidate reads, list moves and blocking pops, set algebra,
|
|
21
|
+
pipelines, transactions and scripts, publish and poll, namespace limits and
|
|
22
|
+
statistics.
|
|
23
|
+
- Documents (13): `doc_replace`, `doc_insert_many`, `doc_delete_many`,
|
|
24
|
+
`doc_bulk_write`, `doc_explain`, collection options, renaming, index rebuilds
|
|
25
|
+
and transactions spanning requests.
|
|
26
|
+
- Vectors (21): batch upserts and deletes, batch, multi-query and by-example
|
|
27
|
+
searches, named vectors, aliases, filters, counts, partitions, text embedding,
|
|
28
|
+
index statistics and collection configuration.
|
|
29
|
+
- Graphs (29): bulk writes, in-place updates, traversals and algorithms
|
|
30
|
+
(components, cycles, k shortest paths, centrality, communities, PageRank and
|
|
31
|
+
more), indexes and constraints, CSV import, Cypher writes and transactions.
|
|
32
|
+
- `TriCoreDB::BulkOp`, the four kinds of item a `doc_bulk_write` takes, and
|
|
33
|
+
`TriCoreDB::CacheOp`, the operations a cache pipeline, transaction or script runs.
|
|
34
|
+
- `TriCoreDB::ScoreBound` and `TriCoreDB::CacheExpiry`.
|
|
35
|
+
- `txn:` on `request` and on the document reads and writes, naming the document
|
|
36
|
+
transaction a call runs inside.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- `in_transaction?` stays true while an outer transaction is open. Since 2026-09-29
|
|
41
|
+
the server opens a nested block when `BEGIN` is sent inside a transaction, and the
|
|
42
|
+
`COMMIT` or `ROLLBACK` that ends the block (answered with `nested_depth`) leaves
|
|
43
|
+
the outer transaction open. The driver used to report the transaction as over, so
|
|
44
|
+
a pool could lend the connection on with it still open.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## [0.2.0] - 2026-09-17
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Following the leader: a request refused with `not_leader` is sent again, to the
|
|
52
|
+
leader the refusal names (over a new connection that replaces the current one) or,
|
|
53
|
+
while no leader is known, to the same node after a pause. On by default;
|
|
54
|
+
`follow_leader: false` restores the previous behaviour, and `leader_retries` (5) and
|
|
55
|
+
`leader_retry_delay` (0.25 s) bound it. Never inside a session transaction.
|
|
56
|
+
- `TriCoreDB::OUTCOME_UNKNOWN`: a request that may or may not have been applied. It is
|
|
57
|
+
never sent again.
|
|
58
|
+
- `Client#address`.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- A `not_leader` error message says whether the client already retried.
|
|
63
|
+
|
|
9
64
|
## [0.1.0] - 2026-09-16
|
|
10
65
|
|
|
11
66
|
First release.
|
|
@@ -42,5 +97,7 @@ First release.
|
|
|
42
97
|
- A value with no SQL form is refused by name instead of being pushed through
|
|
43
98
|
`to_s`, and text that is not valid UTF-8 is refused rather than sent as mojibake.
|
|
44
99
|
|
|
45
|
-
[Unreleased]: https://github.com/trinesh14/tricoredb-sdk-ruby/compare/v0.
|
|
100
|
+
[Unreleased]: https://github.com/trinesh14/tricoredb-sdk-ruby/compare/v0.3.0...HEAD
|
|
101
|
+
[0.3.0]: https://github.com/trinesh14/tricoredb-sdk-ruby/compare/v0.2.0...v0.3.0
|
|
102
|
+
[0.2.0]: https://github.com/trinesh14/tricoredb-sdk-ruby/compare/v0.1.0...v0.2.0
|
|
46
103
|
[0.1.0]: https://github.com/trinesh14/tricoredb-sdk-ruby/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -110,6 +110,9 @@ db.close
|
|
|
110
110
|
| `tls` | `nil` | See [TLS](#tls) |
|
|
111
111
|
| `client_name` | `"tricoredb-ruby/<version>"` | Name reported in the handshake |
|
|
112
112
|
| `features` | every capability | Bitmap announced in the handshake |
|
|
113
|
+
| `follow_leader` | `true` | Send a `not_leader` refusal again, to the leader |
|
|
114
|
+
| `leader_retries` | `5` | How many times a refused request is sent again |
|
|
115
|
+
| `leader_retry_delay` | `0.25` | First pause, in seconds, before asking again; doubles up to 2 |
|
|
113
116
|
|
|
114
117
|
A connection is a single request/response stream, so one connection serves one
|
|
115
118
|
thread. For concurrent work use a [pool](#connection-pool).
|
|
@@ -290,6 +293,183 @@ path["node_path"] # => ["u1", "u2"]
|
|
|
290
293
|
`graph_degree`, `graph_list_nodes`, `graph_list_edges` and `graph_query` for the
|
|
291
294
|
read-only Cypher subset.
|
|
292
295
|
|
|
296
|
+
## Operations added for server 0.1.0-rc.3
|
|
297
|
+
|
|
298
|
+
These need a server at **0.1.0-rc.3 or later**. An older server does not know the
|
|
299
|
+
operation and answers it with an error, which this client raises like any other
|
|
300
|
+
refusal.
|
|
301
|
+
|
|
302
|
+
### Cache
|
|
303
|
+
|
|
304
|
+
| Method | Server operation | What it does |
|
|
305
|
+
| --- | --- | --- |
|
|
306
|
+
| `cache_set_fresh` | `Cache.SetFresh` | Store a value that answers as fresh for the given time and as stale after it, until its TTL (if any) ends |
|
|
307
|
+
| `cache_get_fresh` | `Cache.GetFresh` | Read with stale-while-revalidate and stampede protection |
|
|
308
|
+
| `cache_set_missing` | `Cache.SetMissing` | Negative caching |
|
|
309
|
+
| `cache_warm` | `Cache.Warm` | Cache warming |
|
|
310
|
+
| `cache_mget` | `Cache.MGet` | Read many values in one request |
|
|
311
|
+
| `cache_mset` | `Cache.MSet` | Write many values as one batch, all with the same optional TTL |
|
|
312
|
+
| `cache_set_if` | `Cache.SetIf` | Write a value under a condition, with explicit control over the TTL |
|
|
313
|
+
| `cache_zadd` | `Cache.ZAdd` | Add scored members to a sorted set, or re-score ones already there |
|
|
314
|
+
| `cache_zrem` | `Cache.ZRem` | Remove members from a sorted set, returning how many were present |
|
|
315
|
+
| `cache_zscore` | `Cache.ZScore` | The score of one member, or null when it is not in the set |
|
|
316
|
+
| `cache_zcard` | `Cache.ZCard` | How many members a sorted set holds |
|
|
317
|
+
| `cache_zrank` | `Cache.ZRank` | A member's position in the ordering, or null when it is absent |
|
|
318
|
+
| `cache_zrange` | `Cache.ZRange` | Members by rank, `start..=stop` inclusive |
|
|
319
|
+
| `cache_zrangebyscore` | `Cache.ZRangeByScore` | Members by score, between two bounds |
|
|
320
|
+
| `cache_zincrby` | `Cache.ZIncrBy` | Add to a member's score, creating it at that score when absent |
|
|
321
|
+
| `cache_lock_acquire` | `Cache.LockAcquire` | Take a lease on a lock, or refresh one already held by `owner` |
|
|
322
|
+
| `cache_lock_renew` | `Cache.LockRenew` | Extend a lease the caller still holds |
|
|
323
|
+
| `cache_lock_release` | `Cache.LockRelease` | Give up a lock |
|
|
324
|
+
| `cache_setbit` | `Cache.SetBit` | Set one bit of a string value, growing it with zero bytes if needed |
|
|
325
|
+
| `cache_getbit` | `Cache.GetBit` | Read one bit |
|
|
326
|
+
| `cache_bitcount` | `Cache.BitCount` | Count the set bits, optionally within a byte range `(start, end)` with signed indices |
|
|
327
|
+
| `cache_bitpos` | `Cache.BitPos` | The position of the first bit equal to `bit`, optionally within a byte range, or -1 when there is none |
|
|
328
|
+
| `cache_bitop` | `Cache.BitOp` | Combine several values bitwise and store the result at `dest` |
|
|
329
|
+
| `cache_pfadd` | `Cache.PfAdd` | Add elements to a HyperLogLog, creating it when absent |
|
|
330
|
+
| `cache_pfcount` | `Cache.PfCount` | Estimated number of distinct elements |
|
|
331
|
+
| `cache_pfmerge` | `Cache.PfMerge` | Merge several HyperLogLogs into `dest`, which is included as a source when it already exists |
|
|
332
|
+
| `cache_lmove` | `Cache.LMove` | Atomically pop an element from one end of `source` and push it onto one end of `destination` |
|
|
333
|
+
| `cache_expire_at` | `Cache.ExpireAt` | Set the absolute instant an existing live key dies |
|
|
334
|
+
| `cache_stats` | `Cache.Stats` | What this node's cache is doing |
|
|
335
|
+
| `cache_flush_database` | `Cache.FlushDatabase` | Delete every cache key in this database, across every namespace |
|
|
336
|
+
| `cache_append` | `Cache.Append` | Concatenate bytes onto what a key already holds, and return the new length |
|
|
337
|
+
| `cache_compare_and_swap` | `Cache.CompareAndSwap` | Write only if the key still holds `expected` (null |
|
|
338
|
+
| `cache_getdel` | `Cache.GetDel` | Read a key and delete it in one atomic step |
|
|
339
|
+
| `cache_getset` | `Cache.GetSet` | Write a value and return the one it replaced, atomically |
|
|
340
|
+
| `cache_getex` | `Cache.GetEx` | Read a key and change its expiry in the same atomic step |
|
|
341
|
+
| `cache_ltrim` | `Cache.LTrim` | Keep only the inclusive index range of a list, dropping everything outside it |
|
|
342
|
+
| `cache_sunion` | `Cache.SUnion` | Every member of every named set, without duplicates |
|
|
343
|
+
| `cache_sinter` | `Cache.SInter` | The members present in every named set |
|
|
344
|
+
| `cache_sdiff` | `Cache.SDiff` | The members of the first set that are in none of the rest |
|
|
345
|
+
| `cache_srandmember` | `Cache.SRandMember` | Random members of a set, without removing them |
|
|
346
|
+
| `cache_hincrby` | `Cache.HIncrBy` | Add to one hash field, atomically, returning its new value |
|
|
347
|
+
| `cache_pipeline` | `Cache.Pipeline` | Run cache operations in order in one round trip, each exactly as if it had been sent alone |
|
|
348
|
+
| `cache_transaction` | `Cache.Transaction` | Run cache operations in order holding the cache write lock, so no other writer interleaves with them |
|
|
349
|
+
| `cache_script_load` | `Cache.ScriptLoad` | Store a script |
|
|
350
|
+
| `cache_script_run` | `Cache.ScriptRun` | Run a stored script with these arguments as one transaction |
|
|
351
|
+
| `cache_hot_keys` | `Cache.HotKeys` | The most frequently addressed keys on this node since it started, with estimated counts |
|
|
352
|
+
| `cache_purge_expired` | `Cache.PurgeExpired` | Delete every expired cache key in this database now, rather than leaving it to compaction |
|
|
353
|
+
| `cache_blpop` | `Cache.BLPop` | Pop the head of the first non-empty list among `keys`, waiting up to the timeout for one to receive an element |
|
|
354
|
+
| `cache_brpop` | `Cache.BRPop` | Pop the tail of the first non-empty list among `keys`, waiting up to the timeout for one to receive an element |
|
|
355
|
+
| `cache_set_namespace_limit` | `Cache.SetNamespaceLimit` | Cap a namespace at `max_keys` live keys and `max_bytes` of values |
|
|
356
|
+
| `cache_get_namespace_limit` | `Cache.GetNamespaceLimit` | A namespace's limit and what it holds now |
|
|
357
|
+
| `cache_publish` | `Cache.Publish` | Append a message to a channel, returning the id it was given |
|
|
358
|
+
| `cache_poll` | `Cache.Poll` | The messages after id `after` on these channels (a trailing `*` is a pattern) |
|
|
359
|
+
|
|
360
|
+
### Documents
|
|
361
|
+
|
|
362
|
+
| Method | Server operation | What it does |
|
|
363
|
+
| --- | --- | --- |
|
|
364
|
+
| `doc_replace` | `Document.Replace` | Swap a document's whole body for a new one |
|
|
365
|
+
| `doc_insert_many` | `Document.InsertMany` | Insert several documents as one atomic batch |
|
|
366
|
+
| `doc_delete_many` | `Document.DeleteMany` | Delete every document a filter matches, returning how many went |
|
|
367
|
+
| `doc_rename_collection` | `Document.RenameCollection` | Move a collection, with its documents and indexes, to a new name |
|
|
368
|
+
| `doc_purge_expired` | `Document.PurgeExpired` | Delete the documents a TTL index says have expired, now |
|
|
369
|
+
| `doc_explain` | `Document.Explain` | Run a find and describe how it ran instead of returning its documents |
|
|
370
|
+
| `doc_begin_transaction` | `Document.BeginTransaction` | Open a document transaction spanning requests, returning its id |
|
|
371
|
+
| `doc_commit_transaction` | `Document.CommitTransaction` | Commit a document transaction |
|
|
372
|
+
| `doc_rollback_transaction` | `Document.RollbackTransaction` | Discard a document transaction |
|
|
373
|
+
| `doc_rebuild_index` | `Document.RebuildIndex` | Rebuild one index from the stored documents |
|
|
374
|
+
| `doc_set_collection_options` | `Document.SetCollectionOptions` | Set a collection's options (`schema`, `defaults`, `versioned`), replacing any it had |
|
|
375
|
+
| `doc_get_collection_options` | `Document.GetCollectionOptions` | A collection's options |
|
|
376
|
+
| `doc_bulk_write` | `Document.BulkWrite` | Many inserts, updates, replaces and deletes on one collection, applied in order as one atomic batch |
|
|
377
|
+
|
|
378
|
+
### Vectors
|
|
379
|
+
|
|
380
|
+
| Method | Server operation | What it does |
|
|
381
|
+
| --- | --- | --- |
|
|
382
|
+
| `vector_embed` | `Vector.Embed` | Embed texts with the server's built-in embedder (deterministic feature hashing |
|
|
383
|
+
| `vector_upsert_text` | `Vector.UpsertText` | Embed each text at the collection's dimension and upsert the vectors as one atomic batch |
|
|
384
|
+
| `vector_set_alias` | `Vector.SetAlias` | Point an alias at a collection; a null collection removes it |
|
|
385
|
+
| `vector_list_aliases` | `Vector.ListAliases` | Every alias and the collection it names, as `{alias, collection}` |
|
|
386
|
+
| `vector_add_named_vector` | `Vector.AddNamedVector` | Declare a named vector on a collection |
|
|
387
|
+
| `vector_upsert_named` | `Vector.UpsertNamed` | Write an item's vectors by name (`default` is the collection's own), each with the same metadata |
|
|
388
|
+
| `vector_upsert_many` | `Vector.UpsertMany` | Write many vectors as one atomic batch |
|
|
389
|
+
| `vector_delete_many` | `Vector.DeleteMany` | Delete many vectors by id as one atomic batch, returning how many of the ids were present |
|
|
390
|
+
| `vector_search_batch` | `Vector.SearchBatch` | Run several query vectors against one collection in one request |
|
|
391
|
+
| `vector_delete_by_filter` | `Vector.DeleteByFilter` | Delete every vector whose metadata matches a filter, as one atomic batch |
|
|
392
|
+
| `vector_update_by_filter` | `Vector.UpdateByFilter` | Merge `set` into the metadata of every vector matching a filter, as one atomic batch |
|
|
393
|
+
| `vector_count` | `Vector.Count` | How many vectors a collection holds, or, with a filter, how many match it |
|
|
394
|
+
| `vector_list_partitions` | `Vector.ListPartitions` | The partitions of a collection |
|
|
395
|
+
| `vector_rename_collection` | `Vector.RenameCollection` | Rename a collection, moving its catalog record and every vector with it |
|
|
396
|
+
| `vector_rebuild_index` | `Vector.RebuildIndex` | Rebuild the collection's ANN index from its stored vectors and persist it, clearing every tombstone |
|
|
397
|
+
| `vector_index_stats` | `Vector.IndexStats` | The shape of the collection's ANN index |
|
|
398
|
+
| `vector_measure_recall` | `Vector.MeasureRecall` | Measure the ANN index's recall |
|
|
399
|
+
| `vector_search_multi` | `Vector.SearchMulti` | Search with several query vectors and return one fused ranking |
|
|
400
|
+
| `vector_set_collection_config` | `Vector.SetCollectionConfig` | Change a collection's configuration, returning it |
|
|
401
|
+
| `vector_copy_collection` | `Vector.CopyCollection` | Copy a collection (configuration and every vector) to a new name |
|
|
402
|
+
| `vector_search_by_examples` | `Vector.SearchByExamples` | Search with a query built from examples |
|
|
403
|
+
|
|
404
|
+
### Graphs
|
|
405
|
+
|
|
406
|
+
| Method | Server operation | What it does |
|
|
407
|
+
| --- | --- | --- |
|
|
408
|
+
| `graph_add_many` | `Graph.AddMany` | Write many nodes and edges as one unit |
|
|
409
|
+
| `graph_remove_many` | `Graph.RemoveMany` | Delete many nodes and edges as one unit |
|
|
410
|
+
| `graph_update_node` | `Graph.UpdateNode` | Change an existing node in place, returning it |
|
|
411
|
+
| `graph_update_edge` | `Graph.UpdateEdge` | Change an existing edge in place, returning it |
|
|
412
|
+
| `graph_copy` | `Graph.CopyGraph` | Copy a graph (nodes, edges and schema) to a new name |
|
|
413
|
+
| `graph_dfs` | `Graph.Dfs` | A bounded depth-first walk from a node |
|
|
414
|
+
| `graph_connected_components` | `Graph.ConnectedComponents` | Partition the graph's nodes into connected components, largest first |
|
|
415
|
+
| `graph_detect_cycle` | `Graph.DetectCycle` | Whether the graph contains a cycle (`hasCycle`), and one `cycle` as evidence |
|
|
416
|
+
| `graph_stats` | `Graph.GraphStats` | Counts describing a graph |
|
|
417
|
+
| `graph_subgraph` | `Graph.Subgraph` | The part of the graph within `max_depth` hops of the seeds |
|
|
418
|
+
| `graph_schema` | `Graph.GraphSchema` | What the graph's data looks like |
|
|
419
|
+
| `graph_k_shortest_paths` | `Graph.KShortestPaths` | The `k` cheapest loopless paths between two nodes, cheapest first (default 3, at most 20) |
|
|
420
|
+
| `graph_all_paths` | `Graph.AllPaths` | Every simple path between two nodes of at most `max_depth` edges (default 5, at most 10) |
|
|
421
|
+
| `graph_centrality` | `Graph.Centrality` | Betweenness, closeness or eigenvector centrality |
|
|
422
|
+
| `graph_communities` | `Graph.Communities` | Communities by Louvain modularity optimisation, direction ignored, largest first, with the partition's `modularity` |
|
|
423
|
+
| `graph_triangle_count` | `Graph.TriangleCount` | Triangles and clustering coefficients, direction ignored |
|
|
424
|
+
| `graph_node_similarity` | `Graph.NodeSimilarity` | The nodes whose neighbour sets most resemble a node's, under `results` |
|
|
425
|
+
| `graph_check_consistency` | `Graph.CheckConsistency` | Check a graph's stored records against each other and against the resident mirror |
|
|
426
|
+
| `graph_create_node_index` | `Graph.CreateNodeIndex` | Index node properties for finding nodes |
|
|
427
|
+
| `graph_create_constraint` | `Graph.CreateConstraint` | Enforce a rule on a property of every node (or, with target `edge`, every relationship) carrying a label |
|
|
428
|
+
| `graph_drop_schema_object` | `Graph.DropSchemaObject` | Drop an index or a constraint by name |
|
|
429
|
+
| `graph_list_schema_objects` | `Graph.ListSchemaObjects` | A graph's `indexes` and `constraints` |
|
|
430
|
+
| `graph_find_nodes` | `Graph.FindNodes` | Nodes with a label (when given) whose properties equal the given values, in id order |
|
|
431
|
+
| `graph_import_csv` | `Graph.ImportCsv` | Import nodes and edges from CSV text as one atomic batch, so constraints apply |
|
|
432
|
+
| `graph_page_rank` | `Graph.PageRank` | PageRank over edge direction |
|
|
433
|
+
| `graph_execute` | `Graph.Execute` | Run a Cypher write |
|
|
434
|
+
| `graph_begin_transaction` | `Graph.BeginTransaction` | Open a transaction on a graph, returning its id |
|
|
435
|
+
| `graph_commit_transaction` | `Graph.CommitTransaction` | Apply every statement queued in a graph transaction as one |
|
|
436
|
+
| `graph_rollback_transaction` | `Graph.RollbackTransaction` | Discard a graph transaction and its queued statements |
|
|
437
|
+
`doc_bulk_write` takes its operations from `TriCoreDB::BulkOp` (`insert`,
|
|
438
|
+
`update_one`, `replace`, `delete`), and a cache pipeline, transaction or script takes
|
|
439
|
+
its from `TriCoreDB::CacheOp`:
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
db.doc_bulk_write("people", [
|
|
443
|
+
TriCoreDB::BulkOp.insert({ "name" => "ada" }, id: "a"),
|
|
444
|
+
TriCoreDB::BulkOp.update_one("a", set: { "city" => "Pune" }),
|
|
445
|
+
TriCoreDB::BulkOp.delete("b")
|
|
446
|
+
]) # every operation lands, or none does
|
|
447
|
+
|
|
448
|
+
results = db.cache_pipeline([
|
|
449
|
+
TriCoreDB::CacheOp.set("sessions", "abc", "ada"),
|
|
450
|
+
TriCoreDB::CacheOp.get("sessions", "abc")
|
|
451
|
+
]) # => [{"ok" => {"Json" => {"set" => true}}}, {"ok" => {"CacheValue" => [97, 100, 97]}}]
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
A document transaction is named in each call that runs inside it, and a graph
|
|
455
|
+
transaction queues Cypher writes until it commits:
|
|
456
|
+
|
|
457
|
+
```ruby
|
|
458
|
+
txn = db.doc_begin_transaction
|
|
459
|
+
db.doc_insert("people", { "name" => "ada" }, id: "a", txn: txn)
|
|
460
|
+
db.doc_commit_transaction(txn)
|
|
461
|
+
|
|
462
|
+
graph_txn = db.graph_begin_transaction("social")
|
|
463
|
+
db.graph_execute("social", "CREATE (n:Person {name: 'ada'})", txn: graph_txn)
|
|
464
|
+
db.graph_commit_transaction("social", graph_txn)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
A result with one meaningful field comes back as that value; a larger one comes back
|
|
468
|
+
as the server's own Hash, with the server's field names. Sorted-set bounds come from
|
|
469
|
+
`TriCoreDB::ScoreBound` (a bare number is an inclusive bound) and expiries from
|
|
470
|
+
`TriCoreDB::CacheExpiry`; search `options:` are the server's own names (`offset`,
|
|
471
|
+
`exact`, `using`, `namespace`, ...).
|
|
472
|
+
|
|
293
473
|
## LLM context
|
|
294
474
|
|
|
295
475
|
```ruby
|
|
@@ -329,11 +509,24 @@ Every failure is a `TriCoreDB::Error`. Rescue the class you mean, and branch on
|
|
|
329
509
|
| `ParameterError` | A value with no SQL form; nothing was sent. |
|
|
330
510
|
| `PoolTimeout` | No pooled connection became free in time. |
|
|
331
511
|
|
|
332
|
-
**Leader redirects.** In a cluster
|
|
333
|
-
`code == "not_leader"
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
512
|
+
**Leader redirects.** In a cluster only the leader accepts writes, and a follower
|
|
513
|
+
refuses with `code == "not_leader"` (`error.redirect?`). The client handles that
|
|
514
|
+
itself (`follow_leader: true`, the default):
|
|
515
|
+
|
|
516
|
+
- when the refusal names the leader (`leader_hint`), it opens a connection there with
|
|
517
|
+
the same user, secret and TLS settings (the leader's host is the certificate name),
|
|
518
|
+
sends the request again, and keeps that connection (`db.address` reports it);
|
|
519
|
+
- while an election is in progress (no hint), it waits and asks the same node again,
|
|
520
|
+
pausing 0.25 s, then 0.5 s, up to 2 s;
|
|
521
|
+
- after `leader_retries` (5) it raises the last refusal.
|
|
522
|
+
|
|
523
|
+
This is safe because a `not_leader` refusal was applied nowhere. Nothing is sent again
|
|
524
|
+
inside a session transaction, and never a refusal with `TriCoreDB::OUTCOME_UNKNOWN`: a
|
|
525
|
+
follower handed that request to the leader and lost the answer, or the leader could
|
|
526
|
+
not confirm it, so it may have been applied. The leader's address must be reachable
|
|
527
|
+
from your application; when it is not, the client keeps its connection and waits. A
|
|
528
|
+
pool passes these options to every connection. To handle redirects yourself, connect
|
|
529
|
+
with `follow_leader: false`:
|
|
337
530
|
|
|
338
531
|
```ruby
|
|
339
532
|
begin
|
|
@@ -370,7 +563,9 @@ rake test:integration # the live tests, against a private tricore-server
|
|
|
370
563
|
The scripted-peer tests play the answers a real cluster would send — a `not_leader`
|
|
371
564
|
refusal, a refused handshake, a frame that declares more bytes than it sends — so
|
|
372
565
|
they need no server. The live tests start their own `tricore-server` on an ephemeral
|
|
373
|
-
port: point `TRICORE_SERVER_BIN` at the binary
|
|
566
|
+
port: point `TRICORE_SERVER_BIN` at the binary. To use a server that is already
|
|
567
|
+
running instead, set `TRICORE_HOST`, `TRICORE_PORT`, `TRICORE_USER` and
|
|
568
|
+
`TRICORE_SECRET`. Without either they skip.
|
|
374
569
|
|
|
375
570
|
## License
|
|
376
571
|
|
data/lib/tricoredb/builders.rb
CHANGED
|
@@ -110,6 +110,148 @@ module TriCoreDB
|
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
# What a cache call does to a key's expiry.
|
|
114
|
+
module CacheExpiry
|
|
115
|
+
module_function
|
|
116
|
+
|
|
117
|
+
# Leave the expiry as it is.
|
|
118
|
+
def keep
|
|
119
|
+
"Keep"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Remove the expiry, which is what a plain set does.
|
|
123
|
+
def clear
|
|
124
|
+
"Clear"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Expire `ttl_ms` milliseconds from now.
|
|
128
|
+
# @return [Hash]
|
|
129
|
+
def set(ttl_ms)
|
|
130
|
+
{ "Set" => { "ttl_ms" => Integer(ttl_ms) } }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# One end of a sorted-set score range.
|
|
135
|
+
#
|
|
136
|
+
# JSON has no infinity, so an unbounded end is sent as the largest finite Float,
|
|
137
|
+
# which no finite score lies beyond.
|
|
138
|
+
module ScoreBound
|
|
139
|
+
module_function
|
|
140
|
+
|
|
141
|
+
# A bound that includes members scoring exactly `value`.
|
|
142
|
+
# @return [Hash]
|
|
143
|
+
def inclusive(value)
|
|
144
|
+
bound(value, false)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# A bound that leaves out members scoring exactly `value`.
|
|
148
|
+
# @return [Hash]
|
|
149
|
+
def exclusive(value)
|
|
150
|
+
bound(value, true)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# No lower limit.
|
|
154
|
+
def lowest
|
|
155
|
+
bound(-Float::INFINITY, false)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# No upper limit.
|
|
159
|
+
def highest
|
|
160
|
+
bound(Float::INFINITY, false)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @api private
|
|
164
|
+
def bound(value, exclusive)
|
|
165
|
+
number = Float(value)
|
|
166
|
+
raise ArgumentError, "a score bound must not be NaN" if number.nan?
|
|
167
|
+
|
|
168
|
+
number = number.positive? ? Float::MAX : -Float::MAX if number.infinite?
|
|
169
|
+
{ "value" => number, "exclusive" => exclusive }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Builders for the cache operations a pipeline, a transaction or a script runs.
|
|
174
|
+
# Each is the operation exactly as it would be sent alone.
|
|
175
|
+
#
|
|
176
|
+
# @example
|
|
177
|
+
# db.cache_pipeline([TriCoreDB::CacheOp.set("sessions", "abc", "ada"), TriCoreDB::CacheOp.get("sessions", "abc")])
|
|
178
|
+
module CacheOp
|
|
179
|
+
module_function
|
|
180
|
+
|
|
181
|
+
# A liveness check.
|
|
182
|
+
def ping
|
|
183
|
+
"Ping"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Any cache operation by its server name, with its fields as the server names
|
|
187
|
+
# them. Byte fields are Arrays of byte values.
|
|
188
|
+
# @return [Hash]
|
|
189
|
+
def raw(variant, body)
|
|
190
|
+
{ variant.to_s => body }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @param value [String, Array<Integer>]
|
|
194
|
+
def set(namespace, key, value, ttl_ms: nil)
|
|
195
|
+
raw("Set", "namespace" => namespace.to_s, "key" => key.to_s,
|
|
196
|
+
"value" => value.is_a?(String) ? value.bytes : value, "ttl_ms" => ttl_ms)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def get(namespace, key)
|
|
200
|
+
raw("Get", "namespace" => namespace.to_s, "key" => key.to_s)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def delete(namespace, key)
|
|
204
|
+
raw("Delete", "namespace" => namespace.to_s, "key" => key.to_s)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def exists(namespace, key)
|
|
208
|
+
raw("Exists", "namespace" => namespace.to_s, "key" => key.to_s)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def incr(namespace, key, by = 1)
|
|
212
|
+
raw("Incr", "namespace" => namespace.to_s, "key" => key.to_s, "by" => Integer(by))
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def expire(namespace, key, ttl_ms)
|
|
216
|
+
raw("Expire", "namespace" => namespace.to_s, "key" => key.to_s, "ttl_ms" => Integer(ttl_ms))
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Builders for the operations of {Client#doc_bulk_write}.
|
|
221
|
+
module BulkOp
|
|
222
|
+
module_function
|
|
223
|
+
|
|
224
|
+
# Insert a document, under `id` when one is given.
|
|
225
|
+
# @return [Hash]
|
|
226
|
+
def insert(document, id: nil)
|
|
227
|
+
{ "Insert" => { "id" => id, "document" => document } }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Set or increment fields on one document. `expected_version` applies it only
|
|
231
|
+
# if the document's `_version` is still that.
|
|
232
|
+
# @return [Hash]
|
|
233
|
+
def update_one(id, set: nil, inc: nil, upsert: false, expected_version: nil)
|
|
234
|
+
update = {}
|
|
235
|
+
update["set"] = set if set && !set.empty?
|
|
236
|
+
update["inc"] = inc if inc && !inc.empty?
|
|
237
|
+
{ "UpdateOne" => { "id" => id, "update" => update, "upsert" => upsert ? true : false,
|
|
238
|
+
"expected_version" => expected_version } }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Swap a document's whole body for `document`.
|
|
242
|
+
# @return [Hash]
|
|
243
|
+
def replace(id, document, upsert: false, expected_version: nil)
|
|
244
|
+
{ "Replace" => { "id" => id, "document" => document, "upsert" => upsert ? true : false,
|
|
245
|
+
"expected_version" => expected_version } }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Delete one document by id.
|
|
249
|
+
# @return [Hash]
|
|
250
|
+
def delete(id, expected_version: nil)
|
|
251
|
+
{ "Delete" => { "id" => id, "expected_version" => expected_version } }
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
113
255
|
# Builders for `LlmSource`.
|
|
114
256
|
module LlmSource
|
|
115
257
|
module_function
|