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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE +21 -0
- data/README.md +131 -0
- data/docs/benchmarks.md +135 -0
- data/docs/core.md +123 -0
- data/docs/portable.md +99 -0
- data/docs/releasing.md +37 -0
- data/docs/ruby-profile-v1.md +226 -0
- data/docs/ruby.md +61 -0
- data/examples/indexed_users.rb +23 -0
- data/examples/portable.rb +62 -0
- data/examples/ruby.rb +56 -0
- data/lib/xitdb/context.rb +360 -0
- data/lib/xitdb/conversion.rb +222 -0
- data/lib/xitdb/core/array_list.rb +282 -0
- data/lib/xitdb/core/b_tree.rb +546 -0
- data/lib/xitdb/core/bytes.rb +181 -0
- data/lib/xitdb/core/compaction.rb +172 -0
- data/lib/xitdb/core/crc64.rb +22 -0
- data/lib/xitdb/core/cursor.rb +201 -0
- data/lib/xitdb/core/database.rb +359 -0
- data/lib/xitdb/core/file_store.rb +194 -0
- data/lib/xitdb/core/format.rb +148 -0
- data/lib/xitdb/core/hash_map.rb +371 -0
- data/lib/xitdb/core/hasher.rb +49 -0
- data/lib/xitdb/core/linked_array_list.rb +85 -0
- data/lib/xitdb/core/memory_store.rb +277 -0
- data/lib/xitdb/core/offset_map.rb +85 -0
- data/lib/xitdb/core/replication.rb +242 -0
- data/lib/xitdb/core/sorted_map.rb +361 -0
- data/lib/xitdb/core/transaction.rb +159 -0
- data/lib/xitdb/core.rb +17 -0
- data/lib/xitdb/database.rb +332 -0
- data/lib/xitdb/declarations.rb +73 -0
- data/lib/xitdb/errors.rb +84 -0
- data/lib/xitdb/history.rb +58 -0
- data/lib/xitdb/input_guard.rb +39 -0
- data/lib/xitdb/profiles/portable.rb +407 -0
- data/lib/xitdb/profiles/ruby.rb +173 -0
- data/lib/xitdb/revision.rb +42 -0
- data/lib/xitdb/ruby/codec.rb +123 -0
- data/lib/xitdb/ruby/containers.rb +495 -0
- data/lib/xitdb/ruby/key_encoding.rb +60 -0
- data/lib/xitdb/ruby/ordered_keys.rb +87 -0
- data/lib/xitdb/ruby/scalars.rb +296 -0
- data/lib/xitdb/ruby/wire.rb +146 -0
- data/lib/xitdb/ruby.rb +46 -0
- data/lib/xitdb/snapshot.rb +13 -0
- data/lib/xitdb/streams.rb +148 -0
- data/lib/xitdb/value_walker.rb +192 -0
- data/lib/xitdb/version.rb +5 -0
- data/lib/xitdb/views/array.rb +303 -0
- data/lib/xitdb/views/base.rb +271 -0
- data/lib/xitdb/views/linked_array.rb +18 -0
- data/lib/xitdb/views/map.rb +162 -0
- data/lib/xitdb/views/set.rb +41 -0
- data/lib/xitdb/views/sorted.rb +77 -0
- data/lib/xitdb.rb +20 -0
- data/sig/base.rbs +76 -0
- data/sig/core.rbs +697 -0
- data/sig/manifest.yaml +10 -0
- data/sig/native.rbs +118 -0
- data/sig/public.rbs +427 -0
- data/sig/ruby.rbs +337 -0
- metadata +137 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XitDB
|
|
4
|
+
module Core
|
|
5
|
+
# A counted B-tree for indexed collections.
|
|
6
|
+
class BTree
|
|
7
|
+
HEADER_SIZE = 16
|
|
8
|
+
CAPACITY = 16
|
|
9
|
+
SPLIT_COUNT = 8
|
|
10
|
+
LEAF_SIZE = 146
|
|
11
|
+
BRANCH_SIZE = 274
|
|
12
|
+
# A mutable node used while copying a B-tree path.
|
|
13
|
+
class Node
|
|
14
|
+
attr_accessor :kind, :slots, :counts
|
|
15
|
+
|
|
16
|
+
def initialize(kind, slots, counts)
|
|
17
|
+
@kind = kind
|
|
18
|
+
@slots = slots
|
|
19
|
+
@counts = counts
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def size
|
|
23
|
+
kind.zero? ? slots.size : counts.sum
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
private_constant :Node
|
|
27
|
+
|
|
28
|
+
attr_reader :cursor
|
|
29
|
+
|
|
30
|
+
def initialize(cursor, tag: :linked_array_list)
|
|
31
|
+
@cursor = cursor
|
|
32
|
+
@tag = tag
|
|
33
|
+
unless [:none, @tag].include?(cursor.slot.tag)
|
|
34
|
+
raise InvalidValueError, "slot does not contain a linked array list"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
return unless cursor.slot.tag == :none && cursor.transaction
|
|
38
|
+
|
|
39
|
+
mutate do |transaction|
|
|
40
|
+
pointer = write_node(transaction, Node.new(0, [], []))
|
|
41
|
+
publish(transaction, 0, pointer)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def size
|
|
46
|
+
header.first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def at(index)
|
|
50
|
+
count, pointer = header
|
|
51
|
+
rank = resolve_index(index, count)
|
|
52
|
+
return nil unless rank
|
|
53
|
+
|
|
54
|
+
_path, node, rank = descend(pointer, rank, count)
|
|
55
|
+
node.slots.fetch(rank)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def insert(index, slot)
|
|
59
|
+
validate_slot(slot)
|
|
60
|
+
count, pointer = header
|
|
61
|
+
integer_index(index)
|
|
62
|
+
raise IndexError, "array index outside list" unless index.between?(0, count)
|
|
63
|
+
raise RangeError, "array size exceeds unsigned 64-bit range" if count == Format::UINT64_MAX
|
|
64
|
+
|
|
65
|
+
mutate do |transaction|
|
|
66
|
+
path, node, rank = descend(pointer, index, count, insertion: true)
|
|
67
|
+
node.slots.insert(rank, slot)
|
|
68
|
+
result = write_split(transaction, node, pointer_for(path, pointer))
|
|
69
|
+
path.reverse_each do |parent_pointer, parent, child_index|
|
|
70
|
+
parent.slots[child_index, 1] = result.map { |ptr, _| Slot.new(tag: :index, value: ptr) }
|
|
71
|
+
parent.counts[child_index, 1] = result.map(&:last)
|
|
72
|
+
result = write_split(transaction, parent, parent_pointer)
|
|
73
|
+
end
|
|
74
|
+
root_pointer = if result.size == 1
|
|
75
|
+
result.first.first
|
|
76
|
+
else
|
|
77
|
+
write_node(transaction, Node.new(1, result.map do |ptr, _|
|
|
78
|
+
Slot.new(tag: :index, value: ptr)
|
|
79
|
+
end, result.map(&:last)))
|
|
80
|
+
end
|
|
81
|
+
publish(transaction, count + 1, root_pointer)
|
|
82
|
+
cursor.invalidate_children(from: index)
|
|
83
|
+
end
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def replace(index, slot, owner: nil, collection: false, propagate: true)
|
|
88
|
+
validate_slot(slot)
|
|
89
|
+
count, pointer = header
|
|
90
|
+
rank = resolve_index(index, count)
|
|
91
|
+
raise IndexError, "array index outside list" unless rank
|
|
92
|
+
|
|
93
|
+
mutate do |transaction|
|
|
94
|
+
path, node, rank = descend(pointer, rank, count)
|
|
95
|
+
node.slots[rank] = slot
|
|
96
|
+
child_pointer = write_node(transaction, node, pointer_for(path, pointer))
|
|
97
|
+
path.reverse_each do |parent_pointer, parent, child_index|
|
|
98
|
+
parent.slots[child_index] = Slot.new(tag: :index, value: child_pointer)
|
|
99
|
+
child_pointer = write_node(transaction, parent, parent_pointer)
|
|
100
|
+
end
|
|
101
|
+
publish(transaction, count, child_pointer, propagate: propagate)
|
|
102
|
+
cursor.invalidate_children(index: resolve_index(index, count), except: owner) unless collection
|
|
103
|
+
end
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def delete_at(index)
|
|
108
|
+
count, pointer = header
|
|
109
|
+
rank = resolve_index(index, count)
|
|
110
|
+
return nil unless rank
|
|
111
|
+
|
|
112
|
+
mutate do |transaction|
|
|
113
|
+
path, node, offset = descend(pointer, rank, count)
|
|
114
|
+
removed = node.slots.delete_at(offset)
|
|
115
|
+
old_pointer = pointer_for(path, pointer)
|
|
116
|
+
path.reverse_each do |parent_pointer, parent, child_index|
|
|
117
|
+
rebalance_child(transaction, node, parent, child_index, old_pointer)
|
|
118
|
+
node = parent
|
|
119
|
+
old_pointer = parent_pointer
|
|
120
|
+
end
|
|
121
|
+
root_pointer = collapse_root(transaction, node, old_pointer, count - 1)
|
|
122
|
+
publish(transaction, count - 1, root_pointer)
|
|
123
|
+
cursor.invalidate_children(from: rank)
|
|
124
|
+
removed
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def split(index)
|
|
129
|
+
count, pointer = header
|
|
130
|
+
integer_index(index)
|
|
131
|
+
index += count if index.negative?
|
|
132
|
+
raise IndexError, "array split outside list" unless index.between?(0, count)
|
|
133
|
+
|
|
134
|
+
mutate do |transaction|
|
|
135
|
+
split_root(transaction, [pointer, count, cursor.bound], index).map { |ref| detached(transaction, ref) }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def slice(start, length)
|
|
140
|
+
count, pointer = header
|
|
141
|
+
integer_index(start)
|
|
142
|
+
integer_index(length)
|
|
143
|
+
start += count if start.negative?
|
|
144
|
+
unless start.between?(0, count) && length >= 0 && length <= count - start
|
|
145
|
+
raise IndexError, "array slice outside list"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
mutate do |transaction|
|
|
149
|
+
_left, tail = split_root(transaction, [pointer, count, cursor.bound], start)
|
|
150
|
+
selected, _right = split_root(transaction, tail, length)
|
|
151
|
+
detached(transaction, selected)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def concat(other)
|
|
156
|
+
raise TypeError, "expected BTree" unless other.is_a?(BTree)
|
|
157
|
+
unless cursor.database.equal?(other.cursor.database)
|
|
158
|
+
raise ForeignValueError, "cannot share linked arrays across databases"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
count, pointer = header
|
|
162
|
+
other_count, other_pointer = other.send(:header)
|
|
163
|
+
raise RangeError, "array size exceeds unsigned 64-bit range" if count + other_count > Format::UINT64_MAX
|
|
164
|
+
|
|
165
|
+
mutate do |transaction|
|
|
166
|
+
ref = join_roots(transaction, [pointer, count, cursor.bound],
|
|
167
|
+
[other_pointer, other_count, other.cursor.bound])
|
|
168
|
+
detached(transaction, ref)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def each_from_index(index, &block)
|
|
173
|
+
return enum_for(:each_from_index, index) unless block_given?
|
|
174
|
+
|
|
175
|
+
count, pointer = header
|
|
176
|
+
integer_index(index)
|
|
177
|
+
index += count if index.negative?
|
|
178
|
+
return self unless index >= 0 && index < count
|
|
179
|
+
|
|
180
|
+
path, node, rank = descend(pointer, index, count)
|
|
181
|
+
loop do
|
|
182
|
+
node.slots.drop(rank).each(&block)
|
|
183
|
+
# @type var frame: [Integer, node, Integer]?
|
|
184
|
+
frame = next_frame(path)
|
|
185
|
+
break unless frame
|
|
186
|
+
|
|
187
|
+
parent_pointer, parent, child_index = frame
|
|
188
|
+
child_index += 1
|
|
189
|
+
path << [parent_pointer, parent, child_index]
|
|
190
|
+
subpath, node, rank = descend(parent.slots[child_index].value, 0, parent.counts[child_index],
|
|
191
|
+
ancestors: path.map(&:first))
|
|
192
|
+
path.concat(subpath)
|
|
193
|
+
end
|
|
194
|
+
self
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def next_frame(path)
|
|
200
|
+
until path.empty?
|
|
201
|
+
candidate = path.pop || raise(CorruptionError, "missing B-tree path")
|
|
202
|
+
return candidate if candidate[2] + 1 < candidate[1].slots.size
|
|
203
|
+
end
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def rebalance_child(transaction, node, parent, index, old_pointer)
|
|
208
|
+
if node.slots.empty?
|
|
209
|
+
parent.slots.delete_at(index)
|
|
210
|
+
parent.counts.delete_at(index)
|
|
211
|
+
else
|
|
212
|
+
node, index = merge_neighbor(node, parent, index) if node.slots.size < SPLIT_COUNT && parent.slots.size > 1
|
|
213
|
+
parent.slots[index] = Slot.new(tag: :index, value: write_node(transaction, node, old_pointer))
|
|
214
|
+
parent.counts[index] = node.size
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def merge_neighbor(node, parent, index)
|
|
219
|
+
first = index + 1 < parent.slots.size ? index : index - 1
|
|
220
|
+
neighbor_index = first == index ? first + 1 : first
|
|
221
|
+
neighbor = read_node(parent.slots[neighbor_index].value, parent.counts[neighbor_index])
|
|
222
|
+
raise CorruptionError, "unequal B-tree child heights" unless neighbor.kind == node.kind
|
|
223
|
+
return [node, index] if node.slots.size + neighbor.slots.size > CAPACITY
|
|
224
|
+
|
|
225
|
+
node = first == index ? merge_nodes(node, neighbor) : merge_nodes(neighbor, node)
|
|
226
|
+
parent.slots.delete_at(first + 1)
|
|
227
|
+
parent.counts.delete_at(first + 1)
|
|
228
|
+
[node, first]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def collapse_root(transaction, node, old_pointer, count)
|
|
232
|
+
unless node.kind == 1 && node.slots.size == 1
|
|
233
|
+
node = Node.new(0, [], []) if node.slots.empty?
|
|
234
|
+
return write_node(transaction, node, old_pointer)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
pointer = node.slots.first.value
|
|
238
|
+
# @type var seen: Hash[Integer, bool]
|
|
239
|
+
seen = {}
|
|
240
|
+
loop do
|
|
241
|
+
raise CorruptionError, "cyclic B-tree branch" if seen[pointer]
|
|
242
|
+
|
|
243
|
+
seen[pointer] = true
|
|
244
|
+
child = read_node(pointer, count)
|
|
245
|
+
return pointer unless child.kind == 1 && child.slots.size == 1
|
|
246
|
+
|
|
247
|
+
pointer = child.slots.first.value
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def integer_index(index)
|
|
252
|
+
raise TypeError, "array index must be an Integer" unless index.is_a?(Integer)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def resolve_index(index, count)
|
|
256
|
+
integer_index(index)
|
|
257
|
+
index += count if index.negative?
|
|
258
|
+
index if index >= 0 && index < count
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def read(offset, length, bound: cursor.bound)
|
|
262
|
+
cursor.database.read_at(offset, length, bound: bound)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def header
|
|
266
|
+
slot = cursor.slot
|
|
267
|
+
return [0, nil] if slot.tag == :none
|
|
268
|
+
raise InvalidValueError, "slot does not contain the expected B-tree type" unless slot.tag == @tag
|
|
269
|
+
|
|
270
|
+
Format.pair(read(slot.value, HEADER_SIZE), "Q>Q>")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def read_node(pointer, expected, bound: cursor.bound)
|
|
274
|
+
kind, num = read_node_header(pointer, bound)
|
|
275
|
+
bytes = read(pointer, kind.zero? ? LEAF_SIZE : BRANCH_SIZE, bound: bound)
|
|
276
|
+
slots = node_slots(bytes, 2, CAPACITY).first(num)
|
|
277
|
+
# @type var counts: Array[Integer]
|
|
278
|
+
counts = kind.zero? ? [] : Format.integers(Format.slice(bytes, LEAF_SIZE, 128), "Q>*").first(num)
|
|
279
|
+
node = Node.new(kind, slots, counts)
|
|
280
|
+
validate_node(node, expected)
|
|
281
|
+
node
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def read_node_header(pointer, bound)
|
|
285
|
+
kind, num = Format.pair(read(pointer, 2, bound: bound), "CC")
|
|
286
|
+
unless [0, 1].include?(kind) && num <= CAPACITY && (kind.zero? || num.positive?)
|
|
287
|
+
raise CorruptionError, "invalid B-tree node header"
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
[kind, num]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def node_slots(bytes, start, count)
|
|
294
|
+
Array.new(count) do |index|
|
|
295
|
+
slot = Slot.parse(Format.slice(bytes, start + (index * Slot::SIZE), Slot::SIZE))
|
|
296
|
+
raise CorruptionError, "unknown B-tree slot tag" unless Slot::TAGS.include?(slot.tag)
|
|
297
|
+
|
|
298
|
+
slot
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def validate_node(node, expected)
|
|
303
|
+
case node.kind
|
|
304
|
+
when 1 then validate_branch(node)
|
|
305
|
+
when 0 then validate_leaf(node)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
raise CorruptionError, "invalid B-tree subtree count" unless node.size == expected
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def validate_branch(node)
|
|
312
|
+
return unless node.slots.any? { |slot| slot.tag != :index } || node.counts.any?(&:zero?)
|
|
313
|
+
|
|
314
|
+
raise CorruptionError, "invalid B-tree branch"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def validate_leaf(node)
|
|
318
|
+
raise CorruptionError, "index slot in B-tree leaf" if node.slots.any? { |slot| slot.tag == :index }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def descend(pointer, rank, expected, insertion: false, ancestors: [])
|
|
322
|
+
return [[], Node.new(0, [], []), 0] unless pointer
|
|
323
|
+
|
|
324
|
+
# @type var path: path
|
|
325
|
+
path = []
|
|
326
|
+
seen = ancestors.to_h { |ptr| [ptr, true] }
|
|
327
|
+
loop do
|
|
328
|
+
raise CorruptionError, "cyclic B-tree branch" if seen[pointer]
|
|
329
|
+
|
|
330
|
+
seen[pointer] = true
|
|
331
|
+
node = read_node(pointer, expected)
|
|
332
|
+
return [path, node, rank] if node.kind.zero?
|
|
333
|
+
|
|
334
|
+
i = 0
|
|
335
|
+
while i + 1 < node.slots.size && (insertion ? rank > node.counts[i] : rank >= node.counts[i])
|
|
336
|
+
rank -= node.counts[i]
|
|
337
|
+
i += 1
|
|
338
|
+
end
|
|
339
|
+
path << [pointer, node, i]
|
|
340
|
+
pointer = node.slots[i].value
|
|
341
|
+
expected = node.counts[i]
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def pointer_for(path, root_pointer)
|
|
346
|
+
path.empty? ? root_pointer : path.last[1].slots[path.last[2]].value
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def validate_slot(slot)
|
|
350
|
+
raise TypeError, "expected Slot" unless slot.is_a?(Slot)
|
|
351
|
+
raise CorruptionError, "unknown slot tag" unless Slot::TAGS.include?(slot.tag)
|
|
352
|
+
raise InvalidValueError, "index slot is internal to B-tree branches" if slot.tag == :index
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def mutate
|
|
356
|
+
cursor.slot
|
|
357
|
+
transaction = cursor.transaction
|
|
358
|
+
raise ReadOnlyError, "linked array list is read-only" unless transaction
|
|
359
|
+
|
|
360
|
+
checkpoint = transaction.checkpoint
|
|
361
|
+
begin
|
|
362
|
+
result = yield transaction
|
|
363
|
+
rescue Exception # rubocop:disable Lint/RescueException -- Cleanup must also preserve nonstandard exceptions.
|
|
364
|
+
transaction.restore(checkpoint)
|
|
365
|
+
raise
|
|
366
|
+
else
|
|
367
|
+
transaction.release_checkpoint(checkpoint)
|
|
368
|
+
result
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# References retain each source cursor's read boundary while nodes are shared.
|
|
373
|
+
def node_for(ref)
|
|
374
|
+
pointer = ref[0]
|
|
375
|
+
raise CorruptionError, "missing B-tree pointer" unless pointer
|
|
376
|
+
|
|
377
|
+
read_node(pointer, ref[1], bound: ref[2] || cursor.bound)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def child_ref(ref, node, index)
|
|
381
|
+
[node.slots[index].value, node.counts[index], ref[2]]
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def merge_nodes(left, right)
|
|
385
|
+
raise CorruptionError, "unequal B-tree node kinds" unless left.kind == right.kind
|
|
386
|
+
|
|
387
|
+
Node.new(left.kind, left.slots + right.slots, left.counts + right.counts)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def assemble(transaction, node)
|
|
391
|
+
if node.slots.size <= CAPACITY
|
|
392
|
+
[[write_node(transaction, node), node.size, nil]]
|
|
393
|
+
else
|
|
394
|
+
middle = node.slots.size / 2
|
|
395
|
+
[Node.new(node.kind, node.slots.first(middle), node.counts.first(middle)),
|
|
396
|
+
Node.new(node.kind, node.slots.drop(middle), node.counts.drop(middle))].map do |half|
|
|
397
|
+
[write_node(transaction, half), half.size, nil]
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def branch(refs)
|
|
403
|
+
slots = refs.map do |ref|
|
|
404
|
+
pointer = ref[0]
|
|
405
|
+
raise CorruptionError, "missing B-tree pointer" unless pointer
|
|
406
|
+
|
|
407
|
+
Slot.new(tag: :index, value: pointer)
|
|
408
|
+
end
|
|
409
|
+
Node.new(1, slots, refs.map { |ref| ref[1] })
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def height(ref)
|
|
413
|
+
levels = 0
|
|
414
|
+
# @type var seen: Hash[Integer?, bool]
|
|
415
|
+
seen = {}
|
|
416
|
+
loop do
|
|
417
|
+
raise CorruptionError, "cyclic B-tree branch" if seen[ref[0]]
|
|
418
|
+
|
|
419
|
+
seen[ref[0]] = true
|
|
420
|
+
node = node_for(ref)
|
|
421
|
+
return levels if node.kind.zero?
|
|
422
|
+
|
|
423
|
+
ref = child_ref(ref, node, 0)
|
|
424
|
+
levels += 1
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def join_roots(transaction, left, right)
|
|
429
|
+
return right if left[1].zero?
|
|
430
|
+
return left if right[1].zero?
|
|
431
|
+
|
|
432
|
+
left_height = height(left)
|
|
433
|
+
right_height = height(right)
|
|
434
|
+
# @type var node: node
|
|
435
|
+
# @type var path: Array[[ref, node, Integer]]
|
|
436
|
+
path = []
|
|
437
|
+
while left_height > right_height
|
|
438
|
+
node = node_for(left)
|
|
439
|
+
index = node.slots.size - 1
|
|
440
|
+
path << [left, node, index]
|
|
441
|
+
left = child_ref(left, node, index)
|
|
442
|
+
left_height -= 1
|
|
443
|
+
end
|
|
444
|
+
while right_height > left_height
|
|
445
|
+
node = node_for(right)
|
|
446
|
+
path << [right, node, 0]
|
|
447
|
+
right = child_ref(right, node, 0)
|
|
448
|
+
right_height -= 1
|
|
449
|
+
end
|
|
450
|
+
result = assemble(transaction, merge_nodes(node_for(left), node_for(right)))
|
|
451
|
+
path.reverse_each do |_ref, node, index|
|
|
452
|
+
node.slots[index, 1] = result.map do |ref|
|
|
453
|
+
pointer = ref[0]
|
|
454
|
+
raise CorruptionError, "missing B-tree pointer" unless pointer
|
|
455
|
+
|
|
456
|
+
Slot.new(tag: :index, value: pointer)
|
|
457
|
+
end
|
|
458
|
+
node.counts[index, 1] = result.map { |ref| ref[1] }
|
|
459
|
+
result = assemble(transaction, node)
|
|
460
|
+
end
|
|
461
|
+
result.size == 1 ? result.first : assemble(transaction, branch(result)).first
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def sibling_root(transaction, ref, node, range)
|
|
465
|
+
indices = node.slots.each_index.to_a[range] || []
|
|
466
|
+
refs = indices.map { |index| child_ref(ref, node, index) }
|
|
467
|
+
return [nil, 0, nil] if refs.empty?
|
|
468
|
+
return refs.first if refs.size == 1
|
|
469
|
+
|
|
470
|
+
assemble(transaction, branch(refs)).first
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def split_root(transaction, ref, rank)
|
|
474
|
+
return [[nil, 0, nil], ref] if rank.zero?
|
|
475
|
+
return [ref, [nil, 0, nil]] if rank == ref[1]
|
|
476
|
+
|
|
477
|
+
# @type var path: Array[[ref, node, Integer]]
|
|
478
|
+
path = []
|
|
479
|
+
# @type var seen: Hash[Integer?, bool]
|
|
480
|
+
seen = {}
|
|
481
|
+
node = node_for(ref)
|
|
482
|
+
loop do
|
|
483
|
+
raise CorruptionError, "cyclic B-tree branch" if seen[ref[0]]
|
|
484
|
+
|
|
485
|
+
seen[ref[0]] = true
|
|
486
|
+
node = node_for(ref)
|
|
487
|
+
break if node.kind.zero?
|
|
488
|
+
|
|
489
|
+
index = 0
|
|
490
|
+
while index + 1 < node.slots.size && rank > node.counts[index]
|
|
491
|
+
rank -= node.counts[index]
|
|
492
|
+
index += 1
|
|
493
|
+
end
|
|
494
|
+
path << [ref, node, index]
|
|
495
|
+
ref = child_ref(ref, node, index)
|
|
496
|
+
end
|
|
497
|
+
left = assemble(transaction, Node.new(0, node.slots.first(rank), [])).first
|
|
498
|
+
right = assemble(transaction, Node.new(0, node.slots.drop(rank), [])).first
|
|
499
|
+
path.reverse_each do |parent_ref, parent, index|
|
|
500
|
+
left = join_roots(transaction, sibling_root(transaction, parent_ref, parent, 0...index), left)
|
|
501
|
+
right = join_roots(transaction, right,
|
|
502
|
+
sibling_root(transaction, parent_ref, parent, (index + 1)...parent.slots.size))
|
|
503
|
+
end
|
|
504
|
+
[left, right]
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def detached(transaction, ref)
|
|
508
|
+
pointer = ref[0] || write_node(transaction, Node.new(0, [], []))
|
|
509
|
+
position = transaction.append([ref[1], pointer].pack("Q>Q>"))
|
|
510
|
+
self.class.new(transaction.cursor(slot: Slot.new(tag: :linked_array_list, value: position)))
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def write_node(transaction, node, old_pointer = nil)
|
|
514
|
+
bytes = [node.kind, node.slots.size].pack("CC")
|
|
515
|
+
bytes << node.slots.map(&:to_bytes).join << ("\0".b * ((CAPACITY - node.slots.size) * Slot::SIZE))
|
|
516
|
+
bytes << (node.counts + ([0] * (CAPACITY - node.counts.size))).pack("Q>*") unless node.kind.zero?
|
|
517
|
+
if old_pointer && old_pointer >= transaction.mutable_start
|
|
518
|
+
transaction.write_at(old_pointer, bytes)
|
|
519
|
+
old_pointer
|
|
520
|
+
else
|
|
521
|
+
transaction.append(bytes)
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def write_split(transaction, node, old_pointer)
|
|
526
|
+
return [[write_node(transaction, node, old_pointer), node.size]] if node.slots.size <= CAPACITY
|
|
527
|
+
|
|
528
|
+
left = Node.new(node.kind, node.slots.first(SPLIT_COUNT), node.counts.first(SPLIT_COUNT))
|
|
529
|
+
right = Node.new(node.kind, node.slots.drop(SPLIT_COUNT), node.counts.drop(SPLIT_COUNT))
|
|
530
|
+
[[write_node(transaction, left, old_pointer), left.size], [write_node(transaction, right), right.size]]
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def publish(transaction, count, pointer, propagate: true)
|
|
534
|
+
bytes = [count, pointer].pack("Q>Q>")
|
|
535
|
+
old = cursor.slot
|
|
536
|
+
position = if old.tag == @tag && old.value >= transaction.mutable_start
|
|
537
|
+
transaction.write_at(old.value, bytes)
|
|
538
|
+
old.value
|
|
539
|
+
else
|
|
540
|
+
transaction.append(bytes)
|
|
541
|
+
end
|
|
542
|
+
cursor.update_collection(Slot.new(tag: @tag, value: position), propagate: propagate)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
end
|