ndb 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d05135b99146700c912046914e228098dc99c4bd67181074f39ea399c1d2e375
4
- data.tar.gz: 76be9b065c8505b1a7c59ce69bf66bce0aa179bb6e4368f73facaea10e30c848
3
+ metadata.gz: b4be3ecd3f672ee851429ab1c7c80167be5a7f3d4c40e399eab1d1f301c55f4b
4
+ data.tar.gz: 2601743744f84d4962a395e6168432d60e67d0c557a9da48fa95b245050b4c1d
5
5
  SHA512:
6
- metadata.gz: b3e6fa3bded8d9ee8824f7c0038e2fe74c462cdd95561b20f0a1548c59b40ff825bb1cd41348c9a2b919166fba3ba2ded2c5b58f61133cd729adf2f915e7c0b9
7
- data.tar.gz: 5ace970046bad037e2977795771133017dc594ef83f64ee914799d732fb4b71baca7446e2788464644d9e56e75320d0c7987f87305801730a36a1f043d4a6ceb
6
+ metadata.gz: 4b5625d6c7158f777187868a0bb099155916eafc5774e0d275e2513a5adfd868834f5c15403a6501cf4148af1af43d330f8b12ae72d457b387d26351c678fc71
7
+ data.tar.gz: 0a2f5b44deddc4140f4de275a762da655bbb44e4bacb3dd91178f1ba5a5d77f61abc7aebb7b09886ae6eb9d1c0fb3746e16d888b9c3917692bacfc598154626f
data/README.md CHANGED
@@ -100,6 +100,26 @@ rescue Ndb::VersionConflict
100
100
  end
101
101
  ```
102
102
 
103
+ ## Expiry (TTL like Redis)
104
+
105
+ The collection needs `expires_field`.
106
+
107
+ ```ruby
108
+ ndb.ensure_collection("sessions", { id: "sid", expires_field: "exp" })
109
+
110
+ ndb.insert("sessions", [{ sid: "s1" }], expire_in: 3600) # seconds, or "1h" / "30m" / "1d"
111
+ ndb.update("sessions", "s1", set: { user: "u" }, expire_in: "30m")
112
+
113
+ ndb.expire("sessions", "s1", in: 1800) # => {"id", "v", "expires_at"}
114
+ ndb.expire("sessions", "s1", at: Time.now + 7200, if_version: 2)
115
+ ndb.ttl("sessions", "s1") # => 7199, -1 (never expires), or nil (missing or expired)
116
+ ndb.persist("sessions", "s1") # => {"id", "v"}
117
+ ```
118
+
119
+ - `expire_in` wins over a value of `expires_field` in the same call. `ttl` rounds up to whole seconds.
120
+ - `expire` and `persist` raise `Ndb::NotFound` for a missing or expired document. `Ndb::TtlNotEnabled` means no `expires_field`; `Ndb::TtlDerived` means the expiry comes from `ttl` + `time`.
121
+ - Disk space of expired documents returns after compaction, and fully only with `wal_retention`; see [Freeing disk](https://github.com/thadeu/ndb/blob/main/docs/dev/configuration.md#freeing-disk).
122
+
103
123
  ## Find
104
124
 
105
125
  `find` returns a `Ndb::Page` with `rows` (an array of Hash), `next_cursor`, `plan`, `examined` and `total`.
@@ -264,6 +284,7 @@ All errors inherit from `Ndb::Error`, which has `code` (the daemon code) and `st
264
284
  | `InvalidCursor` | `cursor_invalid`, `cursor_mismatch`, `cursor_expired` |
265
285
  | `WatchLagged` | `watch_lagged` |
266
286
  | `HistoryTruncated` | `history_truncated` |
287
+ | `TtlNotEnabled`, `TtlDerived` | `ttl_not_enabled`, `ttl_derived` |
267
288
  | `ConnectionError` | cannot reach the daemon. Subclasses: `ConnectError` (nothing was sent), `TimeoutError` |
268
289
 
269
290
  Any other code is a plain `Ndb::Error`. A response that is not a daemon error has the code `http_status`.
data/lib/ndb/client.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "json"
4
4
  require "uri"
5
+ require "time"
5
6
 
6
7
  module Ndb
7
8
  Page = Struct.new(:rows, :next_cursor, :plan, :examined, :total)
@@ -40,13 +41,19 @@ module Ndb
40
41
  page(post(coll, "aggregate", Conditions.regexps(body)))
41
42
  end
42
43
 
43
- def insert(coll, docs)
44
- json(post(coll, "insert", insert: docs)).fetch("results")
44
+ # expire_in (seconds, or "30m" / "1h" / "1d") sets the expires_field of
45
+ # every document to now + expire_in, in the same transaction.
46
+ def insert(coll, docs, expire_in: nil)
47
+ body = { insert: docs }
48
+ body[:expire_in] = expire_in if expire_in
49
+
50
+ json(post(coll, "insert", body)).fetch("results")
45
51
  end
46
52
 
47
- def update(coll, id, set: {}, unset: [], if_version: nil)
53
+ def update(coll, id, set: {}, unset: [], if_version: nil, expire_in: nil)
48
54
  change = { id: id, set: set, unset: unset }
49
55
  change[:if_version] = if_version if if_version
56
+ change[:expire_in] = expire_in if expire_in
50
57
 
51
58
  json(post(coll, "update", update: change))
52
59
  end
@@ -58,6 +65,33 @@ module Ndb
58
65
  json(post(coll, "delete", delete: change))
59
66
  end
60
67
 
68
+ # Makes a document expire, like EXPIRE in Redis: in: seconds or "30m", or
69
+ # at: a Time or an RFC 3339 String. Returns {"id", "v", "expires_at"}.
70
+ # Raises NotFound for a missing or expired document, TtlNotEnabled when
71
+ # the collection has no expires_field, TtlDerived when the expiry comes
72
+ # from ttl + time.
73
+ def expire(coll, id, if_version: nil, **time)
74
+ body = { id: id }
75
+ body[:in] = time[:in] if time[:in]
76
+ body[:at] = time[:at].respond_to?(:utc) ? time[:at].utc.iso8601(9) : time[:at] if time[:at]
77
+ body[:if_version] = if_version if if_version
78
+
79
+ json(post(coll, "expire", body))
80
+ end
81
+
82
+ # Removes the expiry of a document, like PERSIST in Redis.
83
+ def persist(coll, id)
84
+ json(post(coll, "persist", id: id))
85
+ end
86
+
87
+ # The seconds left of a document (an Integer, rounded up), -1 when it
88
+ # never expires, nil when it is missing or expired.
89
+ def ttl(coll, id)
90
+ json(request("GET", "#{path(coll)}/ttl/#{URI.encode_www_form_component(id).gsub('+', '%20')}", nil)).fetch("ttl")
91
+ rescue NotFound
92
+ nil
93
+ end
94
+
61
95
  # Streams the changes of a collection for a named consumer: first the ones
62
96
  # after its last ack, then the new ones. Yields one Hash per event (pos,
63
97
  # op, id, v, doc). where is the same Hash/Range conditions as find, and
data/lib/ndb/errors.rb CHANGED
@@ -33,6 +33,10 @@ module Ndb
33
33
  class InvalidQuery < Error; end
34
34
  class InvalidCursor < Error; end
35
35
 
36
+ # The collection has no expires_field, or its expiry comes from ttl + time.
37
+ class TtlNotEnabled < Error; end
38
+ class TtlDerived < Error; end
39
+
36
40
  # The consumer was too slow: call watch again, it resumes from the last ack.
37
41
  class WatchLagged < Error; end
38
42
 
@@ -58,7 +62,9 @@ module Ndb
58
62
  "cursor_mismatch" => InvalidCursor,
59
63
  "cursor_expired" => InvalidCursor,
60
64
  "watch_lagged" => WatchLagged,
61
- "history_truncated" => HistoryTruncated
65
+ "history_truncated" => HistoryTruncated,
66
+ "ttl_not_enabled" => TtlNotEnabled,
67
+ "ttl_derived" => TtlDerived
62
68
  }.freeze
63
69
 
64
70
  def self.error_for(code, message, status)
data/lib/ndb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ndb
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ndb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thadeu