strokedb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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