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