strokedb 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTORS +7 -0
- data/CREDITS +13 -0
- data/README +44 -0
- data/bin/sdbc +2 -0
- data/lib/config/config.rb +161 -0
- data/lib/data_structures/inverted_list.rb +297 -0
- data/lib/data_structures/point_query.rb +24 -0
- data/lib/data_structures/skiplist.rb +302 -0
- data/lib/document/associations.rb +107 -0
- data/lib/document/callback.rb +11 -0
- data/lib/document/coercions.rb +57 -0
- data/lib/document/delete.rb +28 -0
- data/lib/document/document.rb +684 -0
- data/lib/document/meta.rb +261 -0
- data/lib/document/slot.rb +199 -0
- data/lib/document/util.rb +27 -0
- data/lib/document/validations.rb +704 -0
- data/lib/document/versions.rb +106 -0
- data/lib/document/virtualize.rb +82 -0
- data/lib/init.rb +57 -0
- data/lib/stores/chainable_storage.rb +57 -0
- data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
- data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
- data/lib/stores/remote_store.rb +172 -0
- data/lib/stores/skiplist_store/chunk.rb +119 -0
- data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
- data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
- data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
- data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
- data/lib/stores/store.rb +5 -0
- data/lib/sync/chain_sync.rb +38 -0
- data/lib/sync/diff.rb +126 -0
- data/lib/sync/lamport_timestamp.rb +81 -0
- data/lib/sync/store_sync.rb +79 -0
- data/lib/sync/stroke_diff/array.rb +102 -0
- data/lib/sync/stroke_diff/default.rb +21 -0
- data/lib/sync/stroke_diff/hash.rb +186 -0
- data/lib/sync/stroke_diff/string.rb +116 -0
- data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
- data/lib/util/blankslate.rb +42 -0
- data/lib/util/ext/blank.rb +50 -0
- data/lib/util/ext/enumerable.rb +36 -0
- data/lib/util/ext/fixnum.rb +16 -0
- data/lib/util/ext/hash.rb +22 -0
- data/lib/util/ext/object.rb +8 -0
- data/lib/util/ext/string.rb +35 -0
- data/lib/util/inflect.rb +217 -0
- data/lib/util/java_util.rb +9 -0
- data/lib/util/lazy_array.rb +54 -0
- data/lib/util/lazy_mapping_array.rb +64 -0
- data/lib/util/lazy_mapping_hash.rb +46 -0
- data/lib/util/serialization.rb +29 -0
- data/lib/util/trigger_partition.rb +136 -0
- data/lib/util/util.rb +38 -0
- data/lib/util/xml.rb +6 -0
- data/lib/view/view.rb +55 -0
- data/script/console +70 -0
- data/strokedb.rb +75 -0
- 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
|