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