xitdb 0.1.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +28 -0
  3. data/LICENSE +21 -0
  4. data/README.md +131 -0
  5. data/docs/benchmarks.md +135 -0
  6. data/docs/core.md +123 -0
  7. data/docs/portable.md +99 -0
  8. data/docs/releasing.md +37 -0
  9. data/docs/ruby-profile-v1.md +226 -0
  10. data/docs/ruby.md +61 -0
  11. data/examples/indexed_users.rb +23 -0
  12. data/examples/portable.rb +62 -0
  13. data/examples/ruby.rb +56 -0
  14. data/lib/xitdb/context.rb +360 -0
  15. data/lib/xitdb/conversion.rb +222 -0
  16. data/lib/xitdb/core/array_list.rb +282 -0
  17. data/lib/xitdb/core/b_tree.rb +546 -0
  18. data/lib/xitdb/core/bytes.rb +181 -0
  19. data/lib/xitdb/core/compaction.rb +172 -0
  20. data/lib/xitdb/core/crc64.rb +22 -0
  21. data/lib/xitdb/core/cursor.rb +201 -0
  22. data/lib/xitdb/core/database.rb +359 -0
  23. data/lib/xitdb/core/file_store.rb +194 -0
  24. data/lib/xitdb/core/format.rb +148 -0
  25. data/lib/xitdb/core/hash_map.rb +371 -0
  26. data/lib/xitdb/core/hasher.rb +49 -0
  27. data/lib/xitdb/core/linked_array_list.rb +85 -0
  28. data/lib/xitdb/core/memory_store.rb +277 -0
  29. data/lib/xitdb/core/offset_map.rb +85 -0
  30. data/lib/xitdb/core/replication.rb +242 -0
  31. data/lib/xitdb/core/sorted_map.rb +361 -0
  32. data/lib/xitdb/core/transaction.rb +159 -0
  33. data/lib/xitdb/core.rb +17 -0
  34. data/lib/xitdb/database.rb +332 -0
  35. data/lib/xitdb/declarations.rb +73 -0
  36. data/lib/xitdb/errors.rb +84 -0
  37. data/lib/xitdb/history.rb +58 -0
  38. data/lib/xitdb/input_guard.rb +39 -0
  39. data/lib/xitdb/profiles/portable.rb +407 -0
  40. data/lib/xitdb/profiles/ruby.rb +173 -0
  41. data/lib/xitdb/revision.rb +42 -0
  42. data/lib/xitdb/ruby/codec.rb +123 -0
  43. data/lib/xitdb/ruby/containers.rb +495 -0
  44. data/lib/xitdb/ruby/key_encoding.rb +60 -0
  45. data/lib/xitdb/ruby/ordered_keys.rb +87 -0
  46. data/lib/xitdb/ruby/scalars.rb +296 -0
  47. data/lib/xitdb/ruby/wire.rb +146 -0
  48. data/lib/xitdb/ruby.rb +46 -0
  49. data/lib/xitdb/snapshot.rb +13 -0
  50. data/lib/xitdb/streams.rb +148 -0
  51. data/lib/xitdb/value_walker.rb +192 -0
  52. data/lib/xitdb/version.rb +5 -0
  53. data/lib/xitdb/views/array.rb +303 -0
  54. data/lib/xitdb/views/base.rb +271 -0
  55. data/lib/xitdb/views/linked_array.rb +18 -0
  56. data/lib/xitdb/views/map.rb +162 -0
  57. data/lib/xitdb/views/set.rb +41 -0
  58. data/lib/xitdb/views/sorted.rb +77 -0
  59. data/lib/xitdb.rb +20 -0
  60. data/sig/base.rbs +76 -0
  61. data/sig/core.rbs +697 -0
  62. data/sig/manifest.yaml +10 -0
  63. data/sig/native.rbs +118 -0
  64. data/sig/public.rbs +427 -0
  65. data/sig/ruby.rbs +337 -0
  66. metadata +137 -0
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XitDB
4
+ module Core
5
+ # A persistent vector with sixteen-way branches.
6
+ class ArrayList
7
+ include Enumerable
8
+
9
+ HEADER_SIZE = 16
10
+ BLOCK_SIZE = 16 * Slot::SIZE
11
+ EMPTY_BLOCK = ("\0".b * BLOCK_SIZE).freeze
12
+
13
+ attr_reader :cursor
14
+
15
+ def initialize(cursor)
16
+ @cursor = cursor
17
+ unless %i[none array_list].include?(cursor.slot.tag)
18
+ raise InvalidValueError, "slot does not contain an array list"
19
+ end
20
+
21
+ return unless cursor.slot.tag == :none && cursor.transaction
22
+
23
+ mutate do |transaction|
24
+ _count, _pointer, start = writable_header(transaction)
25
+ cursor.update_collection(Slot.new(tag: :array_list, value: start))
26
+ end
27
+ end
28
+
29
+ def size
30
+ header.first
31
+ end
32
+
33
+ def get(index)
34
+ count, pointer = header
35
+ index = resolve_index(index, count)
36
+ return nil unless index
37
+ raise CorruptionError, "missing array pointer" unless pointer
38
+
39
+ read_element(pointer, index, depth(count))
40
+ end
41
+
42
+ def cursor_at(index, create: false)
43
+ count = size
44
+ resolved = resolve_index(index, count)
45
+ if !resolved && create
46
+ raise IndexError, "array index outside list" unless index == count
47
+
48
+ append(Slot.new)
49
+ resolved = index
50
+ end
51
+ return nil unless resolved
52
+
53
+ slot = get(resolved)
54
+ raise CorruptionError, "missing array element" unless slot
55
+
56
+ cursor.array_child(resolved, slot)
57
+ end
58
+
59
+ def append(slot)
60
+ validate_slot(slot)
61
+ mutate do |transaction|
62
+ count, pointer, start = writable_header(transaction)
63
+ raise RangeError, "array size exceeds unsigned 64-bit range" if count == Format::UINT64_MAX
64
+
65
+ if depth(count + 1) > depth(count)
66
+ pointer = transaction.append(Slot.new(tag: :index, value: pointer).to_bytes + Format.slice(EMPTY_BLOCK, Slot::SIZE..))
67
+ end
68
+ position = writable_element(transaction, pointer, count, depth(count + 1))
69
+ transaction.write_at(position, slot.to_bytes)
70
+ transaction.write_at(start, [count + 1, pointer].pack("Q>Q>"))
71
+ cursor.update_collection(Slot.new(tag: :array_list, value: start))
72
+ end
73
+ self
74
+ end
75
+
76
+ def put(index, slot)
77
+ replace_child(index, slot)
78
+ self
79
+ end
80
+
81
+ # A child write updates its current path, including ancestors copied by freeze.
82
+ def replace_child(index, slot, owner: nil, collection: false, propagate: true)
83
+ validate_slot(slot)
84
+ index = resolve_index(index, size)
85
+ raise IndexError, "array index outside list" unless index
86
+
87
+ mutate do |transaction|
88
+ count, pointer, start = writable_header(transaction)
89
+ position = writable_element(transaction, pointer, index, depth(count))
90
+ transaction.write_at(position, slot.to_bytes)
91
+ cursor.update_collection(Slot.new(tag: :array_list, value: start), propagate: propagate)
92
+ cursor.invalidate_children(index: index, except: owner) unless collection
93
+ end
94
+ self
95
+ end
96
+
97
+ def truncate(count)
98
+ Format.integer(count, 0..Format::UINT64_MAX)
99
+ raise IndexError, "array size outside list" if count > size
100
+
101
+ mutate do |transaction|
102
+ previous, pointer, start = writable_header(transaction)
103
+ shift = depth(previous)
104
+ while shift > depth(count)
105
+ slot = read_slot(pointer)
106
+ if slot.empty?
107
+ pointer = transaction.append(EMPTY_BLOCK)
108
+ break
109
+ end
110
+ raise CorruptionError, "invalid array branch" unless slot.tag == :index
111
+
112
+ pointer = slot.value
113
+ shift -= 1
114
+ end
115
+ pointer = copy_block(transaction, pointer)
116
+ transaction.write_at(start, [count, pointer].pack("Q>Q>"))
117
+ cursor.update_collection(Slot.new(tag: :array_list, value: start))
118
+ cursor.invalidate_children(from: count)
119
+ end
120
+ self
121
+ end
122
+
123
+ def each
124
+ return enum_for(:each) unless block_given?
125
+
126
+ count, pointer = header
127
+ return self if count.zero?
128
+
129
+ # @type var stack: Array[[Integer?, Integer, Integer]]
130
+ stack = [[pointer, depth(count), 0]]
131
+ until stack.empty?
132
+ pointer, shift, base = stack.pop || raise(CorruptionError, "missing array branch")
133
+ if pointer.nil?
134
+ [1 << ((shift + 1) * 4), count - base].min.times { yield Slot.new }
135
+ next
136
+ end
137
+ block = read(pointer, BLOCK_SIZE)
138
+ if shift.zero?
139
+ [16, count - base].min.times do |i|
140
+ slot = parse_slot(Format.slice(block, i * Slot::SIZE, Slot::SIZE))
141
+ raise CorruptionError, "index slot in array leaf" if slot.tag == :index
142
+
143
+ yield slot
144
+ end
145
+ else
146
+ stack.concat(branches(block, shift, base, count))
147
+ end
148
+ end
149
+ self
150
+ end
151
+
152
+ private
153
+
154
+ def branches(block, shift, base, count)
155
+ # @type var result: Array[[Integer?, Integer, Integer]]
156
+ result = []
157
+ 15.downto(0) do |index|
158
+ next_base = base + (index * (1 << (shift * 4)))
159
+ next if next_base >= count
160
+
161
+ slot = parse_slot(Format.slice(block, index * Slot::SIZE, Slot::SIZE))
162
+ raise CorruptionError, "invalid array branch" unless slot.empty? || slot.tag == :index
163
+
164
+ result << [slot.empty? ? nil : slot.value, shift - 1, next_base]
165
+ end
166
+ result
167
+ end
168
+
169
+ def header
170
+ slot = cursor.slot
171
+ return [0, nil] if slot.tag == :none
172
+ raise InvalidValueError, "slot does not contain an array list" unless slot.tag == :array_list
173
+
174
+ count, pointer = Format.pair(read(slot.value, HEADER_SIZE), "Q>Q>")
175
+ read(pointer, BLOCK_SIZE)
176
+ [count, pointer]
177
+ end
178
+
179
+ def depth(count)
180
+ [((count - 1).bit_length - 1) / 4, 0].max
181
+ end
182
+
183
+ def resolve_index(index, count)
184
+ raise TypeError, "array index must be an Integer" unless index.is_a?(Integer)
185
+
186
+ index += count if index.negative?
187
+ index if index >= 0 && index < count
188
+ end
189
+
190
+ def read(offset, length)
191
+ cursor.database.read_at(offset, length, bound: cursor.bound)
192
+ end
193
+
194
+ def parse_slot(bytes)
195
+ slot = Slot.parse(bytes)
196
+ raise CorruptionError, "unknown array slot tag" unless Slot::TAGS.include?(slot.tag)
197
+
198
+ slot
199
+ end
200
+
201
+ def read_slot(offset)
202
+ parse_slot(read(offset, Slot::SIZE))
203
+ end
204
+
205
+ def read_element(pointer, index, shift)
206
+ loop do
207
+ read(pointer, BLOCK_SIZE)
208
+ slot = read_slot(pointer + (((index >> (shift * 4)) & 15) * Slot::SIZE))
209
+ if shift.zero?
210
+ raise CorruptionError, "index slot in array leaf" if slot.tag == :index
211
+
212
+ return slot
213
+ end
214
+ return Slot.new if slot.empty?
215
+ raise CorruptionError, "invalid array branch" unless slot.tag == :index
216
+
217
+ pointer = slot.value
218
+ shift -= 1
219
+ end
220
+ end
221
+
222
+ def validate_slot(slot)
223
+ raise TypeError, "expected Slot" unless slot.is_a?(Slot)
224
+ raise CorruptionError, "unknown slot tag" unless Slot::TAGS.include?(slot.tag)
225
+ raise InvalidValueError, "index slot is internal to array branches" if slot.tag == :index
226
+ end
227
+
228
+ def mutate
229
+ cursor.slot
230
+ transaction = cursor.transaction
231
+ raise ReadOnlyError, "array list is read-only" unless transaction
232
+
233
+ checkpoint = transaction.checkpoint
234
+ begin
235
+ yield transaction
236
+ rescue Exception # rubocop:disable Lint/RescueException -- Cleanup must also preserve nonstandard exceptions.
237
+ transaction.restore(checkpoint)
238
+ raise
239
+ else
240
+ transaction.release_checkpoint(checkpoint)
241
+ end
242
+ end
243
+
244
+ def writable_header(transaction)
245
+ count, pointer = header
246
+ if pointer.nil?
247
+ start = transaction.length
248
+ pointer = start + HEADER_SIZE
249
+ transaction.append([0, pointer].pack("Q>Q>") + EMPTY_BLOCK)
250
+ else
251
+ pointer = copy_block(transaction, pointer)
252
+ start = transaction.append([count, pointer].pack("Q>Q>"))
253
+ end
254
+ [count, pointer, start]
255
+ end
256
+
257
+ def copy_block(transaction, pointer)
258
+ block = transaction.read_at(pointer, BLOCK_SIZE)
259
+ pointer < transaction.mutable_start ? transaction.append(block) : pointer
260
+ end
261
+
262
+ def writable_element(transaction, pointer, index, shift)
263
+ loop do
264
+ position = pointer + (((index >> (shift * 4)) & 15) * Slot::SIZE)
265
+ return position if shift.zero?
266
+
267
+ slot = read_slot(position)
268
+ next_pointer = if slot.empty?
269
+ transaction.append(EMPTY_BLOCK)
270
+ elsif slot.tag == :index
271
+ copy_block(transaction, slot.value)
272
+ else
273
+ raise CorruptionError, "invalid array branch"
274
+ end
275
+ transaction.write_at(position, Slot.new(tag: :index, value: next_pointer).to_bytes)
276
+ pointer = next_pointer
277
+ shift -= 1
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end