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