strokedb 0.0.2

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 (59) hide show
  1. data/CONTRIBUTORS +7 -0
  2. data/CREDITS +13 -0
  3. data/README +44 -0
  4. data/bin/sdbc +2 -0
  5. data/lib/config/config.rb +161 -0
  6. data/lib/data_structures/inverted_list.rb +297 -0
  7. data/lib/data_structures/point_query.rb +24 -0
  8. data/lib/data_structures/skiplist.rb +302 -0
  9. data/lib/document/associations.rb +107 -0
  10. data/lib/document/callback.rb +11 -0
  11. data/lib/document/coercions.rb +57 -0
  12. data/lib/document/delete.rb +28 -0
  13. data/lib/document/document.rb +684 -0
  14. data/lib/document/meta.rb +261 -0
  15. data/lib/document/slot.rb +199 -0
  16. data/lib/document/util.rb +27 -0
  17. data/lib/document/validations.rb +704 -0
  18. data/lib/document/versions.rb +106 -0
  19. data/lib/document/virtualize.rb +82 -0
  20. data/lib/init.rb +57 -0
  21. data/lib/stores/chainable_storage.rb +57 -0
  22. data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
  23. data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
  24. data/lib/stores/remote_store.rb +172 -0
  25. data/lib/stores/skiplist_store/chunk.rb +119 -0
  26. data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
  27. data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
  28. data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
  29. data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
  30. data/lib/stores/store.rb +5 -0
  31. data/lib/sync/chain_sync.rb +38 -0
  32. data/lib/sync/diff.rb +126 -0
  33. data/lib/sync/lamport_timestamp.rb +81 -0
  34. data/lib/sync/store_sync.rb +79 -0
  35. data/lib/sync/stroke_diff/array.rb +102 -0
  36. data/lib/sync/stroke_diff/default.rb +21 -0
  37. data/lib/sync/stroke_diff/hash.rb +186 -0
  38. data/lib/sync/stroke_diff/string.rb +116 -0
  39. data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
  40. data/lib/util/blankslate.rb +42 -0
  41. data/lib/util/ext/blank.rb +50 -0
  42. data/lib/util/ext/enumerable.rb +36 -0
  43. data/lib/util/ext/fixnum.rb +16 -0
  44. data/lib/util/ext/hash.rb +22 -0
  45. data/lib/util/ext/object.rb +8 -0
  46. data/lib/util/ext/string.rb +35 -0
  47. data/lib/util/inflect.rb +217 -0
  48. data/lib/util/java_util.rb +9 -0
  49. data/lib/util/lazy_array.rb +54 -0
  50. data/lib/util/lazy_mapping_array.rb +64 -0
  51. data/lib/util/lazy_mapping_hash.rb +46 -0
  52. data/lib/util/serialization.rb +29 -0
  53. data/lib/util/trigger_partition.rb +136 -0
  54. data/lib/util/util.rb +38 -0
  55. data/lib/util/xml.rb +6 -0
  56. data/lib/view/view.rb +55 -0
  57. data/script/console +70 -0
  58. data/strokedb.rb +75 -0
  59. metadata +148 -0
@@ -0,0 +1,106 @@
1
+ module StrokeDB
2
+ class Document
3
+ # Versions is a helper class that is used to navigate through versions. You should not
4
+ # instantiate it directly, but using Document#versions method
5
+ #
6
+ class Versions
7
+ attr_reader :document
8
+ def initialize(document) #:nodoc:
9
+ @document = document
10
+ @cache = {}
11
+ end
12
+
13
+
14
+ #
15
+ # Get document by version.
16
+ #
17
+ # Returns Document instance
18
+ # Returns <tt>nil</tt> if there is no document with given version
19
+ #
20
+ def [](version)
21
+ @cache[version] ||= @document.store.find(document.uuid, version)
22
+ end
23
+
24
+ #
25
+ # Get current version of document
26
+ #
27
+ def current
28
+ document.new? ? document.clone.extend(VersionedDocument) : self[document.version]
29
+ end
30
+
31
+ #
32
+ # Get head version of document
33
+ #
34
+ def head
35
+ document.new? ? document.clone.extend(VersionedDocument) : document.store.find(document.uuid)
36
+ end
37
+
38
+ #
39
+ # Get first version of document
40
+ #
41
+ def first
42
+ document.new? ? document.clone.extend(VersionedDocument) : self[all_preceding_versions.last]
43
+ end
44
+
45
+
46
+
47
+ #
48
+ # Get document with previous version
49
+ #
50
+ # Returns Document instance
51
+ # Returns <tt>nil</tt> if there is no previous version
52
+ #
53
+ def previous
54
+ self[document.previous_version]
55
+ end
56
+
57
+ #
58
+ # Find all document versions, treating current one as a head
59
+ #
60
+ # Returns an Array of version numbers
61
+ #
62
+ def all_versions
63
+ [document.version, *all_preceding_versions]
64
+ end
65
+
66
+ #
67
+ # Get all versions of document including currrent one
68
+ #
69
+ # Returns an Array of Documents
70
+ #
71
+ def all
72
+ all_versions.map{|v| self[v]}
73
+ end
74
+
75
+
76
+ #
77
+ # Find all _previous_ document versions, treating current one as a head
78
+ #
79
+ # Returns an Array of version numbers
80
+ #
81
+ def all_preceding_versions
82
+ if previous_version = document.previous_version
83
+ [previous_version, *self[previous_version].versions.all_preceding_versions]
84
+ else
85
+ []
86
+ end
87
+ end
88
+
89
+ #
90
+ # Find all previous versions of document
91
+ #
92
+ # Returns an Array of Documents
93
+ #
94
+ def all_preceding
95
+ all_preceding_versions.map{|v| self[v]}
96
+ end
97
+
98
+ #
99
+ # Returns <tt>true</tt> if document has no previous versions
100
+ #
101
+ def empty?
102
+ document.previous_version.nil?
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,82 @@
1
+ module StrokeDB
2
+ module Virtualizations
3
+ #
4
+ # Makes a virtual slot. Virtual slot is by all means a regular slot with
5
+ # one exception: it doesn't get serialized on save. Nevertheless its value
6
+ # is preserved while the document is in memory. You can also set :restore
7
+ # option to false (it is true by default), in such case the slot will be
8
+ # removed before save and not restored. It may be useful for ad hoc slots.
9
+ #
10
+ # Virtual slots get validated as usual.
11
+ #
12
+ # User = Meta.new do
13
+ # virtualizes :password, :restore => false
14
+ # validates_presence_of :crypted_password
15
+ # end
16
+ #
17
+ # Regular password is not meant to get serialized in this example, only the
18
+ # crypted one.
19
+ #
20
+ def virtualizes(slotnames, opts = {})
21
+ opts = opts.stringify_keys
22
+
23
+ slotnames = [slotnames] unless slotnames.is_a?(Array)
24
+ slotnames.each {|slotname| register_virtual(slotname, opts)}
25
+ end
26
+
27
+ private
28
+
29
+ # FIXME: willn't usage of instance variables below make a mess in a threaded mode?
30
+ def initialize_virtualizations
31
+ after_validation do |doc|
32
+ @saved_virtual_slots = {}
33
+ @version = doc.version
34
+ @previous_version = doc.previous_version
35
+
36
+ grep_slots(doc, "virtualizes_") do |virtual_slot, meta_slotname|
37
+ if doc.meta[meta_slotname][:restore]
38
+ @saved_virtual_slots[virtual_slot] = doc[virtual_slot]
39
+ end
40
+
41
+ doc.remove_slot!(virtual_slot)
42
+ end
43
+ end
44
+
45
+ after_save do |doc|
46
+ @saved_virtual_slots.each do |slot, value|
47
+ doc[slot] = value
48
+ end
49
+ unless @saved_virtual_slots.empty?
50
+ doc['version'] = @version
51
+
52
+ if @previous_version
53
+ doc['previous_version'] = @previous_version
54
+ else
55
+ doc.remove_slot!('previous_version')
56
+ end
57
+ end
58
+
59
+ @version = nil
60
+ @previous_version = nil
61
+ @saved_virtual_slots = {}
62
+ end
63
+ end
64
+
65
+ def register_virtual(slotname, opts)
66
+ slotname = slotname.to_s
67
+
68
+ restore = opts['restore'].nil? ? true : !!opts['restore']
69
+
70
+ options_hash = {
71
+ :slotname => slotname,
72
+ :restore => restore
73
+ }
74
+
75
+ virtualize_slot = "virtualizes_#{slotname}"
76
+
77
+ @meta_initialization_procs << Proc.new do
78
+ @args.last.reverse_merge!(virtualize_slot => { :meta => name }.merge(options_hash))
79
+ end
80
+ end
81
+ end
82
+ end
data/lib/init.rb ADDED
@@ -0,0 +1,57 @@
1
+ module StrokeDB
2
+ VERSION = '0.0.2' + (RUBY_PLATFORM =~ /java/ ? '-java' : '')
3
+
4
+ # UUID regexp (like 1e3d02cc-0769-4bd8-9113-e033b246b013)
5
+ UUID_RE = /([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/
6
+
7
+ # document version regexp
8
+ VERSION_RE = UUID_RE
9
+
10
+ # following are special UUIDs used by StrokeDB
11
+
12
+ # so called Nil UUID, should be used as special UUID for Meta meta
13
+ NIL_UUID = "00000000-0000-0000-0000-000000000000"
14
+
15
+ # UUID used for DeletedDocument meta
16
+ DELETED_DOCUMENT_UUID = 'e5e0ef20-e10f-4269-bff3-3040a90e194e'
17
+
18
+ # UUID used for StoreInfo meta
19
+ STORE_INFO_UUID = "23e11d2e-e3d3-4c24-afd2-b3316403dd03"
20
+
21
+ # UUID used for Diff meta
22
+ DIFF_UUID = "5704bd39-4a01-405e-bc72-3650ddd89ca4"
23
+
24
+ # UUID used for SynchronizationReport meta
25
+ SYNCHRONIZATION_REPORT_UUID = "8dbaf160-addd-401a-9c29-06b03f70df93"
26
+
27
+ # UUID used for SynchronizationConflict meta
28
+ SYNCHRONIZATION_CONFLICT_UUID = "36fce59c-ee3d-4566-969b-7b152814a314"
29
+
30
+ # UUID used for View meta
31
+ VIEW_UUID = "ced0ad12-7419-4db1-a9f4-bc35e9b64112"
32
+
33
+ # UUID used for ViewCut meta
34
+ VIEWCUT_UUID = "2975630e-c877-4eab-b86c-732e1de1adf5"
35
+
36
+ class <<self
37
+ def default_store
38
+ StrokeDB.default_config.stores[:default] rescue nil
39
+ end
40
+ def default_store=(store)
41
+ cfg = Config.new
42
+ cfg.stores[:default] = store
43
+ StrokeDB.default_config = cfg
44
+ end
45
+ end
46
+
47
+ if ENV['DEBUG'] || $DEBUG
48
+ def DEBUG
49
+ yield
50
+ end
51
+ else
52
+ def DEBUG
53
+ end
54
+ end
55
+
56
+ class NoDefaultStoreError < Exception ; end
57
+ end
@@ -0,0 +1,57 @@
1
+ module StrokeDB
2
+ module ChainableStorage
3
+ def add_chained_storage!(storage)
4
+ @chained_storages ||= {}
5
+ @chained_storages[storage] = []
6
+ class <<self
7
+ alias :save! :save_with_chained_storages!
8
+ end
9
+ storage.add_chained_storage!(self) unless storage.has_chained_storage?(self)
10
+ end
11
+
12
+ def remove_chained_storage!(storage)
13
+ @chained_storages.delete(storage)
14
+ storage.remove_chained_storage!(self) if storage.has_chained_storage?(self)
15
+ if @chained_storages.keys.empty?
16
+ class <<self
17
+ alias :save! :save_without_chained_storages!
18
+ end
19
+ end
20
+ end
21
+
22
+ def has_chained_storage?(storage)
23
+ @chained_storages.nil? ? false : !!@chained_storages[storage]
24
+ end
25
+
26
+ def sync_chained_storages!(origin=nil)
27
+ return unless @chained_storages.is_a?(Hash)
28
+ @chained_storages.each_pair do |storage, savings|
29
+ next if storage == origin
30
+ savings.each {|saving| storage.save!(saving, self)}
31
+ storage.sync_chained_storages!(self)
32
+ @chained_storages[storage] = []
33
+ end
34
+ end
35
+
36
+ def sync_chained_storage!(storage)
37
+ return unless @chained_storages.is_a?(Hash)
38
+ (@chained_storages[storage]||[]).each do |saving|
39
+ storage.save!(saving, self)
40
+ end
41
+ @chained_storages[storage] = []
42
+ end
43
+
44
+ def save_without_chained_storages!(chunk, source=nil)
45
+ perform_save!(chunk)
46
+ end
47
+
48
+ def save_with_chained_storages!(chunk, source=nil)
49
+ perform_save!(chunk)
50
+ (@chained_storages||{}).each_pair do |storage, savings|
51
+ savings << chunk unless storage == source || savings.include?(chunk) # TODO: here we had a bug (storage == document), spec it
52
+ end
53
+ end
54
+
55
+ alias :save! :save_without_chained_storages!
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ module StrokeDB
2
+ class InvertedListFileStorage
3
+ # TODO:
4
+ # include ChainableStorage
5
+
6
+ attr_accessor :path
7
+
8
+ def initialize(opts={})
9
+ opts = opts.stringify_keys
10
+ @path = opts['path']
11
+ end
12
+
13
+ def find_list
14
+ read(file_path)
15
+ end
16
+
17
+ def clear!
18
+ FileUtils.rm_rf @path
19
+ end
20
+
21
+ def save!(list)
22
+ FileUtils.mkdir_p @path
23
+ write(file_path, list)
24
+ end
25
+
26
+ private
27
+
28
+ def read(path)
29
+ return InvertedList.new unless File.exist?(path)
30
+ raw_list = StrokeDB.deserialize(IO.read(path))
31
+ list = InvertedList.new
32
+ # TODO: Optimize!
33
+ raw_list.each do |k, vs|
34
+ vs.each do |v|
35
+ list.insert_attribute(k, v)
36
+ end
37
+ end
38
+ list
39
+ end
40
+
41
+ def write(path, list)
42
+ raw_list = {}
43
+ # TODO: Optimize!
44
+ list.each do |n|
45
+ raw_list[n.key] = n.values
46
+ end
47
+ File.open path, "w+" do |f|
48
+ f.write StrokeDB.serialize(raw_list)
49
+ end
50
+ end
51
+
52
+ def file_path
53
+ "#{@path}/INVERTED_INDEX"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,49 @@
1
+ module StrokeDB
2
+ class InvertedListIndex
3
+ attr_accessor :storage, :document_store
4
+ def initialize(storage)
5
+ @storage = storage
6
+ @list = nil
7
+ end
8
+
9
+ def find_uuids(*args)
10
+ list.find(*args)
11
+ end
12
+
13
+ def find(*args)
14
+ find_uuids(*args).map do |uuid|
15
+ @document_store.find(uuid)
16
+ end
17
+ end
18
+
19
+ def insert(doc)
20
+ slots = indexable_slots_for_doc(doc)
21
+ q = PointQuery.new(slots)
22
+ list.insert(q.slots, doc.uuid)
23
+ end
24
+
25
+ def delete(doc)
26
+ slots = indexable_slots_for_doc(doc)
27
+ q = PointQuery.new(slots)
28
+ list.delete(q.slots, doc.uuid)
29
+ end
30
+
31
+ def save!
32
+ @storage.save!(list)
33
+ end
34
+
35
+ private
36
+
37
+ def indexable_slots_for_doc(doc)
38
+ raw_slots = doc.to_raw
39
+ nkeys = doc.meta['non_indexable_slots']
40
+ nkeys.each{|nk| raw_slots.delete(nk) } if nkeys
41
+ raw_slots
42
+ end
43
+
44
+ def list
45
+ @list ||= @storage.find_list
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,172 @@
1
+ module StrokeDB
2
+ class Store
3
+ def remote_server(addr, protocol=:drb)
4
+ case protocol
5
+ when :drb
6
+ RemoteStore::DRb::Server.new(self,"#{addr}")
7
+ else
8
+ raise "No #{protocol} protocol"
9
+ end
10
+ end
11
+ end
12
+ module RemoteStore
13
+ module DRb
14
+ class Client
15
+
16
+ attr_reader :addr
17
+
18
+ def initialize(addr)
19
+ @addr = addr
20
+ ::DRb.start_service
21
+ @server = ::DRbObject.new(nil, addr)
22
+ end
23
+
24
+ def find(*args)
25
+ safe_document_from_undumped(@server.find(*args))
26
+ end
27
+
28
+ def search(*args)
29
+ @server.search(*args).map{ |e| safe_document_from_undumped(e) }
30
+ end
31
+
32
+ def exists?(uuid, version=nil)
33
+ @server.exists?(uuid, version)
34
+ end
35
+
36
+ def head_version(uuid)
37
+ raw_doc = find(uuid,nil, :no_instantiation => true)
38
+ return raw_doc['version'] if raw_doc
39
+ nil
40
+ end
41
+
42
+ def save!(*args)
43
+ result = @server.save!(*args)
44
+ if result.is_a?(Document)
45
+ safe_document_from_undumped(result)
46
+ end
47
+ result
48
+ end
49
+
50
+ def each(options = {})
51
+ @server.each(options) do |doc_without_store|
52
+ safe_document_from_undumped(doc_without_store)
53
+ end
54
+ end
55
+
56
+ def timestamp
57
+ @server.timestamp
58
+ end
59
+
60
+ def next_timestamp
61
+ @server.next_timestamp
62
+ end
63
+
64
+ def uuid
65
+ @server.uuid
66
+ end
67
+
68
+ def document
69
+ result = @server.document
70
+ safe_document_from_undumped(result)
71
+ end
72
+
73
+ def empty?
74
+ @server.empty?
75
+ end
76
+
77
+ def inspect
78
+ @server.inspect
79
+ end
80
+
81
+ def index_store
82
+ @server.index_store
83
+ end
84
+
85
+ private
86
+
87
+ def safe_document_from_undumped(doc_without_store)
88
+ doc_without_store.instance_variable_set(:@store, self) if doc_without_store
89
+ doc_without_store
90
+ end
91
+
92
+ end
93
+
94
+ class Server
95
+ attr_reader :store, :addr, :thread
96
+ def initialize(store, addr)
97
+ @store, @addr = store, addr
98
+ @mutex = Mutex.new
99
+ end
100
+
101
+ def start
102
+ ::DRb.start_service(addr, self)
103
+ @thread = ::DRb.thread
104
+ end
105
+
106
+ def find(*args)
107
+ @mutex.synchronize { @store.find(*args) }
108
+ end
109
+
110
+ def search(*args)
111
+ @mutex.synchronize { @store.search(*args) }
112
+ end
113
+
114
+ def exists?(*args)
115
+ !!@mutex.synchronize { @store.exists?(*args) }
116
+ end
117
+
118
+ def head_version(uuid)
119
+ raw_doc = @mutex.synchronize { find(uuid, nil, :no_instantiation => true) }
120
+ return raw_doc['version'] if raw_doc
121
+ nil
122
+ end
123
+
124
+ def save!(document)
125
+ document.instance_variable_set(:@store, @store)
126
+ @mutex.synchronize { @store.save!(document) }
127
+ end
128
+
129
+ def each(options = {}, &block)
130
+ @mutex.synchronize { @store.each(options, &block) }
131
+ end
132
+
133
+ def timestamp
134
+ @mutex.synchronize { @store.timestamp }
135
+ end
136
+
137
+ def next_timestamp
138
+ @mutex.synchronize { @store.next_timestamp }
139
+ end
140
+
141
+ def uuid
142
+ @store.uuid
143
+ end
144
+
145
+ def document
146
+ @mutex.synchronize { @store.document }
147
+ end
148
+
149
+ def empty?
150
+ @mutex.synchronize { @store.empty? }
151
+ end
152
+
153
+ def autosync!
154
+ @mutex.synchronize { @store.autosync! }
155
+ end
156
+
157
+ def stop_autosync!
158
+ @mutex.synchronize { @store.stop_autosync! }
159
+ end
160
+
161
+ def inspect
162
+ @store.inspect
163
+ end
164
+
165
+ def index_store
166
+ @store.index_store
167
+ end
168
+
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,119 @@
1
+ module StrokeDB
2
+ class Chunk
3
+ attr_accessor :skiplist, :next_chunk, :prev_chunk, :uuid, :cut_level, :timestamp
4
+ attr_accessor :next_chunk_uuid
5
+ attr_accessor :store_uuid
6
+ def initialize(cut_level)
7
+ @skiplist, @cut_level = Skiplist.new({}, nil, cut_level), cut_level
8
+ end
9
+
10
+ def insert(uuid, raw_doc, __cheaters_level = nil, __timestamp = nil)
11
+ @uuid ||= uuid
12
+ __cheaters_level ||= $DEBUG_CHEATERS_LEVEL
13
+ a, new_list = skiplist.insert(uuid, raw_doc, __cheaters_level, __timestamp)
14
+ if new_list
15
+ tmp = Chunk.new(@cut_level)
16
+ tmp.skiplist = new_list
17
+ tmp.next_chunk = @next_chunk if @next_chunk
18
+ @next_chunk = tmp
19
+ @next_chunk.uuid = uuid
20
+ end
21
+ [self, @next_chunk]
22
+ end
23
+
24
+ def delete(uuid)
25
+ skiplist.delete(uuid)
26
+ end
27
+
28
+ def find(uuid, default = nil)
29
+ skiplist.find(uuid, default)
30
+ end
31
+
32
+ def find_node(uuid)
33
+ skiplist.find_node(uuid)
34
+ end
35
+
36
+ def find_nearest(uuid, default = nil)
37
+ skiplist.find_nearest(uuid, default)
38
+ end
39
+
40
+ # Finds next node across separate chunks
41
+ def find_next_node(node)
42
+ chunk = self
43
+ node2 = node.next
44
+ if node2.is_a?(Skiplist::TailNode)
45
+ chunk = chunk.next_chunk
46
+ unless chunk.nil?
47
+ node2 = chunk.first_node
48
+ else
49
+ node2 = nil
50
+ end
51
+ end
52
+ node2
53
+ end
54
+
55
+
56
+ def first_uuid
57
+ skiplist.first_node.key
58
+ end
59
+
60
+ def first_node
61
+ skiplist.first_node
62
+ end
63
+
64
+ def size
65
+ skiplist.size
66
+ end
67
+
68
+ def each(&block)
69
+ skiplist.each &block
70
+ end
71
+
72
+ # Raw format
73
+
74
+ # TODO: lazify
75
+ def self.from_raw(raw)
76
+ chunk = Chunk.new(raw['cut_level'])
77
+ chunk.uuid = raw['uuid']
78
+ chunk.next_chunk_uuid = raw['next_uuid']
79
+ chunk.timestamp = raw['timestamp']
80
+ chunk.store_uuid = raw['store_uuid']
81
+ chunk.skiplist.raw_insert(raw['nodes']) do |rn|
82
+ [rn['key'], rn['value'], rn['forward'].size, rn['timestamp']]
83
+ end
84
+ yield(chunk) if block_given?
85
+ chunk
86
+ end
87
+
88
+ def to_raw
89
+ # enumerate nodes
90
+ skiplist.each_with_index do |node, i|
91
+ node._serialized_index = i
92
+ end
93
+
94
+ # now we know keys' positions right in the nodes
95
+ nodes = skiplist.map do |node|
96
+ {
97
+ 'key' => node.key,
98
+ 'forward' => node.forward.map{|n| n._serialized_index || 0 },
99
+ 'value' => node.value,
100
+ 'timestamp' => node.timestamp
101
+ }
102
+ end
103
+ {
104
+ 'nodes' => nodes,
105
+ 'cut_level' => @cut_level,
106
+ 'uuid' => @uuid,
107
+ # TODO: may not be needed
108
+ 'next_uuid' => next_chunk ? next_chunk.uuid : nil,
109
+ 'timestamp' => @timestamp,
110
+ 'store_uuid' => @store_uuid
111
+ }
112
+ end
113
+
114
+ def eql?(chunk)
115
+ chunk.uuid == @uuid && chunk.skiplist.eql?(@skiplist)
116
+ end
117
+
118
+ end
119
+ end