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,261 @@
|
|
1
|
+
module StrokeDB
|
2
|
+
# Meta is basically a type. Imagine the following document:
|
3
|
+
#
|
4
|
+
# some_apple:
|
5
|
+
# weight: 3oz
|
6
|
+
# color: green
|
7
|
+
# price: $3
|
8
|
+
#
|
9
|
+
# Each apple is a fruit and a product in this case (because it has price).
|
10
|
+
#
|
11
|
+
# we can express it by assigning metas to document like this:
|
12
|
+
#
|
13
|
+
# some_apple:
|
14
|
+
# meta: [Fruit, Product]
|
15
|
+
# weight: 3oz
|
16
|
+
# color: green
|
17
|
+
# price: $3
|
18
|
+
#
|
19
|
+
# In document slots metas store references to metadocument.
|
20
|
+
#
|
21
|
+
# Document class will be extended by modules Fruit and Product.
|
22
|
+
module Meta
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def new(*args, &block)
|
26
|
+
mod = Module.new
|
27
|
+
args = args.unshift(nil) if args.empty? || args.first.is_a?(Hash)
|
28
|
+
args << {} unless args.last.is_a?(Hash)
|
29
|
+
mod.module_eval do
|
30
|
+
@args = args
|
31
|
+
@meta_initialization_procs = []
|
32
|
+
@metas = [self]
|
33
|
+
extend Meta
|
34
|
+
extend Associations
|
35
|
+
extend Validations
|
36
|
+
extend Coercions
|
37
|
+
extend Virtualizations
|
38
|
+
extend Util
|
39
|
+
end
|
40
|
+
mod.module_eval(&block) if block_given?
|
41
|
+
mod.module_eval do
|
42
|
+
initialize_associations
|
43
|
+
initialize_validations
|
44
|
+
initialize_coercions
|
45
|
+
initialize_virtualizations
|
46
|
+
end
|
47
|
+
if meta_name = extract_meta_name(*args)
|
48
|
+
Object.const_set(meta_name, mod)
|
49
|
+
end
|
50
|
+
mod
|
51
|
+
end
|
52
|
+
|
53
|
+
def document(store=nil)
|
54
|
+
raise NoDefaultStoreError.new unless store ||= StrokeDB.default_store
|
55
|
+
unless meta_doc = store.find(NIL_UUID)
|
56
|
+
meta_doc = Document.create!(store, :name => Meta.name, :uuid => NIL_UUID)
|
57
|
+
end
|
58
|
+
meta_doc
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def extract_meta_name(*args)
|
64
|
+
if args.first.is_a?(Hash)
|
65
|
+
args.first[:name]
|
66
|
+
else
|
67
|
+
args[1][:name] unless args.empty?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def +(meta)
|
74
|
+
if is_a?(Module) && meta.is_a?(Module)
|
75
|
+
new_meta = Module.new
|
76
|
+
instance_variables.each do |iv|
|
77
|
+
new_meta.instance_variable_set(iv, instance_variable_get(iv) ? instance_variable_get(iv).clone : nil)
|
78
|
+
end
|
79
|
+
new_meta.instance_variable_set(:@metas, @metas.clone)
|
80
|
+
new_meta.instance_variable_get(:@metas) << meta
|
81
|
+
new_meta.module_eval do
|
82
|
+
extend Meta
|
83
|
+
end
|
84
|
+
new_meta_name = new_meta.instance_variable_get(:@metas).map{|m| m.name}.join('__')
|
85
|
+
Object.send(:remove_const, new_meta_name) rescue nil
|
86
|
+
Object.const_set(new_meta_name, new_meta)
|
87
|
+
new_meta
|
88
|
+
elsif is_a?(Document) && meta.is_a?(Document)
|
89
|
+
(Document.new(store, self.to_raw.except('uuid','version','previous_version'), true) +
|
90
|
+
Document.new(store, meta.to_raw.except('uuid','version','previous_version'), true)).extend(Meta).make_immutable!
|
91
|
+
else
|
92
|
+
raise "Can't + #{self.class} and #{meta.class}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
CALLBACKS = %w(on_initialization on_load before_save after_save when_slot_not_found on_new_document on_validation
|
97
|
+
after_validation on_set_slot)
|
98
|
+
|
99
|
+
CALLBACKS.each do |callback_name|
|
100
|
+
module_eval %{
|
101
|
+
def #{callback_name}(uid=nil, &block)
|
102
|
+
add_callback('#{callback_name}', uid, &block)
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def new(*args, &block)
|
108
|
+
args = args.clone
|
109
|
+
args << {} unless args.last.is_a?(Hash)
|
110
|
+
args.last[:meta] = @metas
|
111
|
+
doc = Document.new(*args, &block)
|
112
|
+
doc
|
113
|
+
end
|
114
|
+
|
115
|
+
def create!(*args, &block)
|
116
|
+
new(*args, &block).save!
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Finds all documents matching given parameters. The simplest form of
|
121
|
+
# +find+ call is without any parameters. This returns all documents
|
122
|
+
# belonging to the meta as an array.
|
123
|
+
#
|
124
|
+
# User = Meta.new
|
125
|
+
# all_users = User.find
|
126
|
+
#
|
127
|
+
# Another form is to find a document by its UUID:
|
128
|
+
#
|
129
|
+
# specific_user = User.find("1e3d02cc-0769-4bd8-9113-e033b246b013")
|
130
|
+
#
|
131
|
+
# If the UUID is not found, nil is returned.
|
132
|
+
#
|
133
|
+
# Most prominent search uses slot values as criteria:
|
134
|
+
#
|
135
|
+
# short_fat_joes = User.find(:name => "joe", :weight => 110, :height => 167)
|
136
|
+
#
|
137
|
+
# All matching documents are returned as an array.
|
138
|
+
#
|
139
|
+
# In all described cases the default store is used. You may also specify
|
140
|
+
# another store as the first argument:
|
141
|
+
#
|
142
|
+
# all_my_users = User.find(my_store)
|
143
|
+
# all_my_joes = User.find(my_store, :name => "joe")
|
144
|
+
# oh_my = User.find(my_store, "1e3d02cc-0769-4bd8-9113-e033b246b013")
|
145
|
+
#
|
146
|
+
def find(*args)
|
147
|
+
if args.empty? || !args.first.respond_to?(:search)
|
148
|
+
raise NoDefaultStoreError unless StrokeDB.default_store
|
149
|
+
|
150
|
+
args = args.unshift(StrokeDB.default_store)
|
151
|
+
end
|
152
|
+
|
153
|
+
unless args.size == 1 || args.size == 2
|
154
|
+
raise ArgumentError, "Invalid arguments for find"
|
155
|
+
end
|
156
|
+
|
157
|
+
store = args[0]
|
158
|
+
opt = { :meta => @metas.map {|m| m.document(store)} }
|
159
|
+
|
160
|
+
case args[1]
|
161
|
+
when String
|
162
|
+
raise ArgumentError, "Invalid UUID" unless args[1].match(UUID_RE)
|
163
|
+
|
164
|
+
store.search(opt.merge({ :uuid => args[1] })).first
|
165
|
+
when Hash
|
166
|
+
store.search opt.merge(args[1])
|
167
|
+
when nil
|
168
|
+
store.search opt
|
169
|
+
else
|
170
|
+
raise ArgumentError, "Invalid search criteria for find"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Similar to +find+, but a creates document with appropriate slot values if
|
176
|
+
# not found.
|
177
|
+
#
|
178
|
+
# If found, returned is only the first result.
|
179
|
+
#
|
180
|
+
def find_or_create(*args)
|
181
|
+
result = find(*args)
|
182
|
+
result.empty? ? create!(*args) : result.first
|
183
|
+
end
|
184
|
+
|
185
|
+
def inspect
|
186
|
+
if is_a?(Module)
|
187
|
+
name
|
188
|
+
else
|
189
|
+
pretty_print
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
alias :to_s :inspect
|
194
|
+
|
195
|
+
def document(store=nil)
|
196
|
+
metadocs = @metas.map do |m|
|
197
|
+
@args = m.instance_variable_get(:@args)
|
198
|
+
make_document(store)
|
199
|
+
end
|
200
|
+
metadocs.size > 1 ? metadocs.inject { |a, b| a + b }.make_immutable! : metadocs.first
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def make_document(store=nil)
|
206
|
+
raise NoDefaultStoreError.new unless store ||= StrokeDB.default_store
|
207
|
+
@meta_initialization_procs.each {|proc| proc.call }.clear
|
208
|
+
|
209
|
+
values = @args.clone.select{|a| Hash === a}.first
|
210
|
+
values[:meta] = Meta.document(store)
|
211
|
+
values[:name] ||= name
|
212
|
+
|
213
|
+
if meta_doc = find_meta_doc(values, store)
|
214
|
+
values[:version] = meta_doc.version
|
215
|
+
values[:uuid] = meta_doc.uuid
|
216
|
+
args = [store, values]
|
217
|
+
meta_doc = updated_meta_doc(args) if changed?(meta_doc, args)
|
218
|
+
else
|
219
|
+
args = [store, values]
|
220
|
+
meta_doc = Document.new(*args)
|
221
|
+
meta_doc.extend(Meta)
|
222
|
+
meta_doc.save!
|
223
|
+
end
|
224
|
+
meta_doc
|
225
|
+
end
|
226
|
+
|
227
|
+
def find_meta_doc(values, store)
|
228
|
+
if uuid = values[:uuid]
|
229
|
+
store.find(uuid)
|
230
|
+
else
|
231
|
+
store.search({ :name => values[:name], :meta => Meta.document(store) }).first
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def changed?(meta_doc, args)
|
236
|
+
!(Document.new(*args).to_raw.except('previous_version') == meta_doc.to_raw.except('previous_version'))
|
237
|
+
end
|
238
|
+
|
239
|
+
def updated_meta_doc(args)
|
240
|
+
new_doc = Document.new(*args)
|
241
|
+
new_doc.instance_variable_set(:@saved, true)
|
242
|
+
new_doc.send!(:update_version!, nil)
|
243
|
+
new_doc.save!
|
244
|
+
end
|
245
|
+
|
246
|
+
def add_callback(name,uid=nil, &block)
|
247
|
+
@callbacks ||= []
|
248
|
+
@callbacks << Callback.new(self, name, uid, &block)
|
249
|
+
end
|
250
|
+
|
251
|
+
def setup_callbacks(doc)
|
252
|
+
return unless @callbacks
|
253
|
+
@callbacks.each do |callback|
|
254
|
+
doc.callbacks[callback.name] ||= []
|
255
|
+
doc.callbacks[callback.name] << callback
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module StrokeDB
|
2
|
+
class HashSlotValue < LazyMappingHash
|
3
|
+
def with_modification_callback(&block)
|
4
|
+
@modification_callback = block
|
5
|
+
self
|
6
|
+
end
|
7
|
+
def []=(*args)
|
8
|
+
super(*args)
|
9
|
+
@modification_callback.call if @modification_callback
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ArraySlotValue < LazyMappingArray
|
14
|
+
def with_modification_callback(&block)
|
15
|
+
@modification_callback = block
|
16
|
+
self
|
17
|
+
end
|
18
|
+
def []=(*args)
|
19
|
+
super(*args)
|
20
|
+
@modification_callback.call if @modification_callback
|
21
|
+
end
|
22
|
+
def push(*args)
|
23
|
+
super(*args)
|
24
|
+
@modification_callback.call if @modification_callback
|
25
|
+
end
|
26
|
+
def <<(*args)
|
27
|
+
super(*args)
|
28
|
+
@modification_callback.call if @modification_callback
|
29
|
+
end
|
30
|
+
def unshift(*args)
|
31
|
+
super(*args)
|
32
|
+
@modification_callback.call if @modification_callback
|
33
|
+
end
|
34
|
+
|
35
|
+
def include?(v)
|
36
|
+
case v
|
37
|
+
when VersionedDocument
|
38
|
+
super(v)
|
39
|
+
when Document
|
40
|
+
v.versions.all.find{|d| super(d)} # FIXME: versions.all could be pretty slow
|
41
|
+
else
|
42
|
+
super(v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class DocumentReferenceValue < String
|
49
|
+
attr_reader :str
|
50
|
+
attr_accessor :doc
|
51
|
+
def initialize(str, doc, __cached_value = nil)
|
52
|
+
@str, @doc = str, doc
|
53
|
+
@cached_value = __cached_value
|
54
|
+
super(str)
|
55
|
+
end
|
56
|
+
def load
|
57
|
+
case self
|
58
|
+
when VERSIONREF
|
59
|
+
if doc.head?
|
60
|
+
@cached_value || @cached_value = doc.store.find($1) || self
|
61
|
+
else
|
62
|
+
@cached_value || @cached_value = doc.store.find($1,$2) || self
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
def inspect
|
67
|
+
"#<DocRef #{self[0,5]}..>"
|
68
|
+
end
|
69
|
+
alias :to_raw :str
|
70
|
+
|
71
|
+
def ==(v)
|
72
|
+
case v
|
73
|
+
when DocumentReferenceValue
|
74
|
+
v.str == str
|
75
|
+
when Document
|
76
|
+
v == self
|
77
|
+
else
|
78
|
+
str == v
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class Slot
|
86
|
+
attr_reader :doc, :value, :name
|
87
|
+
|
88
|
+
def initialize(doc, name = nil)
|
89
|
+
@doc, @name = doc, name
|
90
|
+
@decoded = {}
|
91
|
+
end
|
92
|
+
|
93
|
+
def value=(v)
|
94
|
+
v = doc.send!(:execute_callbacks, :on_set_slot, name, v) || v unless name == 'meta'
|
95
|
+
@value = decode_value(enforce_collections(encode_value(v, true), true))
|
96
|
+
end
|
97
|
+
|
98
|
+
def value
|
99
|
+
@value.is_a?(DocumentReferenceValue) ? @value.load : @value
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_raw
|
103
|
+
raw_value.to_optimized_raw
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
def raw_value=(v)
|
109
|
+
self.value = decode_value(v)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def raw_value
|
115
|
+
result = encode_value(@value)
|
116
|
+
enforce_collections(result)
|
117
|
+
end
|
118
|
+
|
119
|
+
def encode_value(v, skip_documents=false)
|
120
|
+
case v
|
121
|
+
when Document
|
122
|
+
skip_documents ? v : DocumentReferenceValue.new(v.__reference__, doc, v)
|
123
|
+
when Module
|
124
|
+
if v.respond_to?(:document)
|
125
|
+
v.document(doc.store)
|
126
|
+
else
|
127
|
+
raise ArgumentError, "#{v.class} is not a valid slot value type"
|
128
|
+
end
|
129
|
+
when Array
|
130
|
+
LazyMappingArray.new(v).map_with do |element|
|
131
|
+
encode_value(element, skip_documents)
|
132
|
+
end.unmap_with do |element|
|
133
|
+
decode_value(element)
|
134
|
+
end
|
135
|
+
when Hash
|
136
|
+
LazyMappingHash.new(v).map_with do |element|
|
137
|
+
encode_value(element, skip_documents)
|
138
|
+
end.unmap_with do |element|
|
139
|
+
decode_value(element)
|
140
|
+
end
|
141
|
+
when Range, Regexp
|
142
|
+
"@!Dump:#{StrokeDB::serialize(v)}"
|
143
|
+
when Symbol
|
144
|
+
v.to_s
|
145
|
+
when Time, String, Numeric, TrueClass, FalseClass, NilClass
|
146
|
+
v
|
147
|
+
else
|
148
|
+
raise ArgumentError, "#{v.class} is not a valid slot value type"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def decode_value(v)
|
153
|
+
case v
|
154
|
+
when VERSIONREF
|
155
|
+
DocumentReferenceValue.new(v, doc)
|
156
|
+
when /^@!Dump:/
|
157
|
+
StrokeDB::deserialize(v[7, v.length-7])
|
158
|
+
when Array
|
159
|
+
ArraySlotValue.new(v).map_with do |element|
|
160
|
+
decoded = decode_value(element)
|
161
|
+
@decoded[decoded] ||= decoded.is_a?(DocumentReferenceValue) ? decoded.load : decoded
|
162
|
+
end.unmap_with do |element|
|
163
|
+
encode_value(element)
|
164
|
+
end.with_modification_callback do
|
165
|
+
doc.send!(:update_version!, nil)
|
166
|
+
end
|
167
|
+
when Hash
|
168
|
+
HashSlotValue.new(v).map_with do |element|
|
169
|
+
decoded = decode_value(element)
|
170
|
+
@decoded[decoded] ||= decoded.is_a?(DocumentReferenceValue) ? decoded.load : decoded
|
171
|
+
end.unmap_with do |element|
|
172
|
+
encode_value(element)
|
173
|
+
end.with_modification_callback do
|
174
|
+
doc.send!(:update_version!, nil)
|
175
|
+
end
|
176
|
+
when Symbol
|
177
|
+
v.to_s
|
178
|
+
else
|
179
|
+
v
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def enforce_collections(v, skip_documents = false)
|
184
|
+
return v unless v.is_a?(Array) || v.is_a?(Hash)
|
185
|
+
case v
|
186
|
+
when Array
|
187
|
+
v.map{|v| enforce_collections(encode_value(v, skip_documents))}
|
188
|
+
when Hash
|
189
|
+
h = {}
|
190
|
+
v.each_pair do |k, v|
|
191
|
+
h[enforce_collections(encode_value(k, skip_documents))] = enforce_collections(encode_value(v, skip_documents))
|
192
|
+
end
|
193
|
+
h
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module StrokeDB
|
2
|
+
module Meta
|
3
|
+
module Util
|
4
|
+
def grep_slots(doc, prefix)
|
5
|
+
doc.meta.slotnames.each do |slotname|
|
6
|
+
if slotname[0..(prefix.length - 1)] == prefix
|
7
|
+
yield slotname[prefix.length..-1], slotname
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_condition(condition)
|
13
|
+
case condition
|
14
|
+
when Symbol, String then return
|
15
|
+
else
|
16
|
+
raise ArgumentError, ":if/:unless clauses need to be either a symbol or string (slotname or method name)"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate_condition(condition, doc)
|
21
|
+
case condition
|
22
|
+
when String then doc.send(condition)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|