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,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
|