strokedb 0.0.2.1 → 0.0.2.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 (192) hide show
  1. data/README +18 -20
  2. data/bench.html +4001 -0
  3. data/bin/strokedb +14 -0
  4. data/examples/movies.rb +105 -0
  5. data/examples/movies2.rb +97 -0
  6. data/examples/strokewiki/README +28 -0
  7. data/examples/strokewiki/view/edit.xhtml +27 -0
  8. data/examples/strokewiki/view/new.xhtml +26 -0
  9. data/examples/strokewiki/view/pages.xhtml +27 -0
  10. data/examples/strokewiki/view/show.xhtml +40 -0
  11. data/examples/strokewiki/view/versions.xhtml +25 -0
  12. data/examples/strokewiki/wiki.rb +106 -0
  13. data/examples/todo.rb +92 -0
  14. data/lib/strokedb.rb +85 -0
  15. data/lib/{config → strokedb}/config.rb +14 -9
  16. data/lib/strokedb/console.rb +87 -0
  17. data/lib/strokedb/core_ext.rb +10 -0
  18. data/lib/{util/ext → strokedb/core_ext}/blank.rb +1 -1
  19. data/lib/{util/ext → strokedb/core_ext}/enumerable.rb +0 -0
  20. data/lib/{util/ext → strokedb/core_ext}/fixnum.rb +0 -0
  21. data/lib/strokedb/core_ext/float.rb +4 -0
  22. data/lib/{util/ext → strokedb/core_ext}/hash.rb +0 -0
  23. data/lib/strokedb/core_ext/infinity.rb +33 -0
  24. data/lib/strokedb/core_ext/kernel.rb +41 -0
  25. data/lib/strokedb/core_ext/object.rb +16 -0
  26. data/lib/{util/ext → strokedb/core_ext}/string.rb +28 -1
  27. data/lib/strokedb/core_ext/symbol.rb +13 -0
  28. data/lib/strokedb/data_structures.rb +5 -0
  29. data/lib/strokedb/data_structures/chunked_skiplist.rb +123 -0
  30. data/lib/{data_structures → strokedb/data_structures}/inverted_list.rb +0 -0
  31. data/lib/{data_structures → strokedb/data_structures}/point_query.rb +0 -0
  32. data/lib/strokedb/data_structures/simple_skiplist.rb +350 -0
  33. data/lib/{data_structures → strokedb/data_structures}/skiplist.rb +1 -1
  34. data/lib/{document → strokedb}/document.rb +180 -71
  35. data/lib/{document → strokedb/document}/callback.rb +0 -0
  36. data/lib/{document → strokedb/document}/delete.rb +2 -2
  37. data/lib/strokedb/document/dsl.rb +4 -0
  38. data/lib/{document → strokedb/document/dsl}/associations.rb +0 -0
  39. data/lib/{document → strokedb/document/dsl}/coercions.rb +0 -0
  40. data/lib/strokedb/document/dsl/meta_dsl.rb +7 -0
  41. data/lib/{document → strokedb/document/dsl}/validations.rb +26 -21
  42. data/lib/{document → strokedb/document/dsl}/virtualize.rb +0 -0
  43. data/lib/{document → strokedb/document}/meta.rb +92 -29
  44. data/lib/{document → strokedb/document}/slot.rb +17 -5
  45. data/lib/{document → strokedb/document}/util.rb +0 -0
  46. data/lib/{document → strokedb/document}/versions.rb +2 -2
  47. data/lib/strokedb/index.rb +2 -0
  48. data/lib/strokedb/nsurl.rb +24 -0
  49. data/lib/strokedb/store.rb +149 -0
  50. data/lib/strokedb/stores.rb +6 -0
  51. data/lib/{stores → strokedb/stores}/chainable_storage.rb +20 -14
  52. data/lib/strokedb/stores/file_storage.rb +118 -0
  53. data/lib/{stores/inverted_list_index → strokedb/stores}/inverted_list_file_storage.rb +50 -0
  54. data/lib/strokedb/stores/memory_storage.rb +80 -0
  55. data/lib/{stores → strokedb/stores}/remote_store.rb +10 -4
  56. data/lib/strokedb/sync.rb +4 -0
  57. data/lib/{sync → strokedb/sync}/chain_sync.rb +0 -0
  58. data/lib/{sync → strokedb/sync}/diff.rb +12 -1
  59. data/lib/{sync/stroke_diff → strokedb/sync/diff}/array.rb +1 -1
  60. data/lib/{sync/stroke_diff → strokedb/sync/diff}/default.rb +0 -0
  61. data/lib/{sync/stroke_diff → strokedb/sync/diff}/hash.rb +1 -1
  62. data/lib/{sync/stroke_diff → strokedb/sync/diff}/string.rb +1 -1
  63. data/lib/{sync → strokedb/sync}/lamport_timestamp.rb +0 -0
  64. data/lib/{sync → strokedb/sync}/store_sync.rb +15 -7
  65. data/lib/strokedb/transaction.rb +78 -0
  66. data/lib/{util → strokedb}/util.rb +14 -7
  67. data/lib/strokedb/util/attach_dsl.rb +29 -0
  68. data/lib/{util → strokedb/util}/blankslate.rb +0 -0
  69. data/lib/strokedb/util/class_optimization.rb +93 -0
  70. data/lib/{util → strokedb/util}/inflect.rb +0 -0
  71. data/lib/strokedb/util/java_util.rb +13 -0
  72. data/lib/{util → strokedb/util}/lazy_array.rb +0 -0
  73. data/lib/{util → strokedb/util}/lazy_mapping_array.rb +4 -0
  74. data/lib/{util → strokedb/util}/lazy_mapping_hash.rb +0 -0
  75. data/lib/{util → strokedb/util}/serialization.rb +21 -0
  76. data/lib/strokedb/util/uuid.rb +159 -0
  77. data/lib/{util → strokedb/util}/xml.rb +0 -0
  78. data/lib/{view → strokedb}/view.rb +2 -2
  79. data/lib/strokedb/volumes.rb +5 -0
  80. data/lib/strokedb/volumes/archive_volume.rb +165 -0
  81. data/lib/strokedb/volumes/block_volume.rb +169 -0
  82. data/lib/strokedb/volumes/distributed_pointer.rb +43 -0
  83. data/lib/strokedb/volumes/fixed_length_skiplist_volume.rb +109 -0
  84. data/lib/strokedb/volumes/map_volume.rb +268 -0
  85. data/meta/MANIFEST +175 -0
  86. data/script/console +2 -70
  87. data/spec/integration/remote_store_spec.rb +70 -0
  88. data/spec/integration/search_spec.rb +76 -0
  89. data/spec/integration/spec_helper.rb +1 -0
  90. data/spec/lib/spec_helper.rb +1 -0
  91. data/spec/lib/strokedb/config_spec.rb +250 -0
  92. data/spec/lib/strokedb/core_ext/blank_spec.rb +20 -0
  93. data/spec/lib/strokedb/core_ext/extract_spec.rb +42 -0
  94. data/spec/lib/strokedb/core_ext/float_spec.rb +62 -0
  95. data/spec/lib/strokedb/core_ext/infinity_spec.rb +40 -0
  96. data/spec/lib/strokedb/core_ext/spec_helper.rb +1 -0
  97. data/spec/lib/strokedb/core_ext/string_spec.rb +25 -0
  98. data/spec/lib/strokedb/core_ext/symbol_spec.rb +8 -0
  99. data/spec/lib/strokedb/data_structures/chunked_skiplist_spec.rb +144 -0
  100. data/spec/lib/strokedb/data_structures/inverted_list_spec.rb +172 -0
  101. data/spec/lib/strokedb/data_structures/simple_skiplist_spec.rb +200 -0
  102. data/spec/lib/strokedb/data_structures/skiplist_spec.rb +253 -0
  103. data/spec/lib/strokedb/data_structures/spec_helper.rb +1 -0
  104. data/spec/lib/strokedb/document/associations_spec.rb +319 -0
  105. data/spec/lib/strokedb/document/callbacks_spec.rb +134 -0
  106. data/spec/lib/strokedb/document/coercions_spec.rb +110 -0
  107. data/spec/lib/strokedb/document/document_spec.rb +1063 -0
  108. data/spec/lib/strokedb/document/meta_meta_spec.rb +30 -0
  109. data/spec/lib/strokedb/document/meta_spec.rb +435 -0
  110. data/spec/lib/strokedb/document/metaslot_spec.rb +43 -0
  111. data/spec/lib/strokedb/document/slot_spec.rb +130 -0
  112. data/spec/lib/strokedb/document/spec_helper.rb +1 -0
  113. data/spec/lib/strokedb/document/validations_spec.rb +1081 -0
  114. data/spec/lib/strokedb/document/virtualize_spec.rb +80 -0
  115. data/spec/lib/strokedb/nsurl_spec.rb +73 -0
  116. data/spec/lib/strokedb/spec_helper.rb +1 -0
  117. data/spec/lib/strokedb/stores/chained_storages_spec.rb +116 -0
  118. data/spec/lib/strokedb/stores/spec_helper.rb +1 -0
  119. data/spec/lib/strokedb/stores/store_spec.rb +201 -0
  120. data/spec/lib/strokedb/stores/transaction_spec.rb +107 -0
  121. data/spec/lib/strokedb/sync/chain_sync_spec.rb +43 -0
  122. data/spec/lib/strokedb/sync/diff_spec.rb +111 -0
  123. data/spec/lib/strokedb/sync/lamport_timestamp_spec.rb +174 -0
  124. data/spec/lib/strokedb/sync/slot_diff_spec.rb +164 -0
  125. data/spec/lib/strokedb/sync/spec_helper.rb +1 -0
  126. data/spec/lib/strokedb/sync/store_sync_spec.rb +181 -0
  127. data/spec/lib/strokedb/sync/stroke_diff/array_spec.rb +97 -0
  128. data/spec/lib/strokedb/sync/stroke_diff/complex_spec.rb +58 -0
  129. data/spec/lib/strokedb/sync/stroke_diff/hash_spec.rb +144 -0
  130. data/spec/lib/strokedb/sync/stroke_diff/scalar_spec.rb +23 -0
  131. data/spec/lib/strokedb/sync/stroke_diff/spec_helper.rb +25 -0
  132. data/spec/lib/strokedb/sync/stroke_diff/string_spec.rb +61 -0
  133. data/spec/lib/strokedb/util/attach_dsl_spec.rb +45 -0
  134. data/spec/lib/strokedb/util/inflect_spec.rb +14 -0
  135. data/spec/lib/strokedb/util/lazy_array_spec.rb +157 -0
  136. data/spec/lib/strokedb/util/lazy_mapping_array_spec.rb +174 -0
  137. data/spec/lib/strokedb/util/lazy_mapping_hash_spec.rb +92 -0
  138. data/spec/lib/strokedb/util/spec_helper.rb +1 -0
  139. data/spec/lib/strokedb/util/uuid_spec.rb +46 -0
  140. data/spec/lib/strokedb/view_spec.rb +228 -0
  141. data/spec/lib/strokedb/volumes/archive_volume_spec.rb +105 -0
  142. data/spec/lib/strokedb/volumes/block_volume_spec.rb +100 -0
  143. data/spec/lib/strokedb/volumes/distributed_pointer_spec.rb +14 -0
  144. data/spec/lib/strokedb/volumes/fixed_length_skiplist_volume_spec.rb +177 -0
  145. data/spec/lib/strokedb/volumes/map_volume_spec.rb +172 -0
  146. data/spec/lib/strokedb/volumes/spec_helper.rb +1 -0
  147. data/spec/regression/docref_spec.rb +94 -0
  148. data/spec/regression/meta_spec.rb +23 -0
  149. data/spec/regression/spec_helper.rb +1 -0
  150. data/spec/regression/sync_spec.rb +36 -0
  151. data/spec/spec.opts +7 -0
  152. data/spec/spec_helper.rb +37 -0
  153. data/spec/temp/storages/TIMESTAMP +1 -0
  154. data/spec/temp/storages/UUID +1 -0
  155. data/spec/temp/storages/database-sync/TIMESTAMP +1 -0
  156. data/spec/temp/storages/database-sync/UUID +1 -0
  157. data/spec/temp/storages/database-sync/config +1 -0
  158. data/spec/temp/storages/database-sync/file/LAST +1 -0
  159. data/spec/temp/storages/database-sync/file/bd/f6/bdf675e5-8a7b-494e-97f2-f74a14ccd95d.av +0 -0
  160. data/spec/temp/storages/database-sync/file/uindex.wal +0 -0
  161. data/spec/temp/storages/database-sync/inverted_list_file/INVERTED_INDEX +1 -0
  162. data/spec/temp/storages/inverted_list_storage/INVERTED_INDEX +0 -0
  163. data/strokedb.gemspec +120 -0
  164. data/task/benchmark.task +9 -0
  165. data/task/ditz.task +30 -0
  166. data/task/echoe.rb +17 -0
  167. data/task/rcov.task +50 -0
  168. data/task/rdoc.task +10 -0
  169. data/task/rspec.task +0 -0
  170. data/vendor/java_inline.rb +106 -0
  171. data/vendor/rbmodexcl/mrimodexcl.rb +82 -0
  172. data/vendor/rbmodexcl/rbmodexcl.rb +5 -0
  173. data/vendor/rbmodexcl/rbxmodexcl.rb +48 -0
  174. data/vendor/rbmodexcl/spec/unextend_spec.rb +50 -0
  175. data/vendor/rbmodexcl/spec/uninclude_spec.rb +26 -0
  176. metadata +271 -79
  177. data/CONTRIBUTORS +0 -7
  178. data/CREDITS +0 -13
  179. data/bin/sdbc +0 -2
  180. data/lib/init.rb +0 -57
  181. data/lib/stores/inverted_list_index/inverted_list_index.rb +0 -49
  182. data/lib/stores/skiplist_store/chunk.rb +0 -119
  183. data/lib/stores/skiplist_store/chunk_storage.rb +0 -21
  184. data/lib/stores/skiplist_store/file_chunk_storage.rb +0 -44
  185. data/lib/stores/skiplist_store/memory_chunk_storage.rb +0 -37
  186. data/lib/stores/skiplist_store/skiplist_store.rb +0 -217
  187. data/lib/stores/store.rb +0 -5
  188. data/lib/sync/stroke_diff/stroke_diff.rb +0 -9
  189. data/lib/util/ext/object.rb +0 -8
  190. data/lib/util/java_util.rb +0 -9
  191. data/lib/util/trigger_partition.rb +0 -136
  192. data/strokedb.rb +0 -75
@@ -25,11 +25,38 @@ class String
25
25
  gsub(/^.*::/, '')
26
26
  end
27
27
 
28
+ def modulize
29
+ return '' unless include?('::') && self[0,2] != '::'
30
+ self.gsub(/^(.+)::(#{demodulize})$/,'\\1')
31
+ end
32
+
28
33
  def constantize
34
+ if /^meta:/ =~ self
35
+ return StrokeDB::META_CACHE[Meta.make_uuid_from_fullname(self)]
36
+ end
29
37
  unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
30
38
  raise NameError, "#{self.inspect} is not a valid constant name!"
31
39
  end
32
-
33
40
  Object.module_eval("::#{$1}", __FILE__, __LINE__)
34
41
  end
42
+
43
+ def /(o)
44
+ File.join(self, o.to_s)
45
+ end
46
+
47
+ def unindent!
48
+ self.gsub!(/^\n/, '').gsub!(/^#{self.match(/^\s*/)[0]}/, '')
49
+ end
50
+
51
+ def underscore
52
+ self.to_s.gsub(/::/, '/').
53
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
54
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
55
+ tr("-", "_").
56
+ downcase
57
+ end
58
+
59
+ def lines
60
+ self.split("\n").size
61
+ end
35
62
  end
@@ -0,0 +1,13 @@
1
+ class Symbol
2
+ # ==== Parameters
3
+ # o<String>:: The path component to join with the symbol.
4
+ #
5
+ # ==== Returns
6
+ # String:: The original path concatenated with o.
7
+ #
8
+ # ==== Examples
9
+ # :lib / :core_ext #=> "lib/core_ext"
10
+ def /(o)
11
+ File.join(self.to_s, o.to_s)
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ require 'data_structures/simple_skiplist'
2
+ require 'data_structures/skiplist'
3
+ require 'data_structures/chunked_skiplist'
4
+ require 'data_structures/inverted_list'
5
+ require 'data_structures/point_query'
@@ -0,0 +1,123 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../util/class_optimization')
2
+ module StrokeDB
3
+ # ChunkedSkiplist (CS) implements a distributed, concurrently accessible
4
+ # skiplist using SimpleSkiplist (SL) as building blocks.
5
+ # Each instance contains a single instance of SimpleSkiplist.
6
+ # Higher-level CS store references to lower-level SL as SL "data".
7
+ # Lowest-level CS contains actual data.
8
+ #
9
+ # Regular state of the chunks (square brackets denote references):
10
+ #
11
+ # ______ ___________________
12
+ # / \ / \
13
+ # HEAD -> C1[ C2, C3 ], C2[ C4, C5 ], C3[ C6, C7 ], C4[data], ...
14
+ # => \__________________/
15
+ #
16
+ # Initial state is a single lowest-level chunk:
17
+ #
18
+ # HEAD -> C1[data]
19
+ #
20
+ # When higher-level node is inserted, new skiplist is created.
21
+ # Old skiplist is moved to a new chunk, current chunk uppers its level.
22
+ #
23
+ # ASYNCHRONOUS CONCURRENT INSERT
24
+ #
25
+ # SKiplists, by their nature, allow you to concurrently insert and
26
+ # delete nodes. However, very little number of nodes must be locked
27
+ # during update. In our implementation, we lock a whole chunk if it is
28
+ # modified. Higher-level chunks are modified rarely, so they are not
29
+ # locked most of the time. Different chunks could be updated concurrently.
30
+ # Read-only concurrent access is always possible no matter what nodes are
31
+ # locked for modification.
32
+ #
33
+ # ChunkedSkiplist has an API for asynchronous data access useful for
34
+ # cöoperative multitasking, but it is also thread-safe for preemtive
35
+ # multitasking, which is kinda nice feature, but is not to be evaluated
36
+ # in a real-world applications.
37
+ #
38
+ # Chunked #find
39
+ #
40
+ # Find may return an actual data or a reference to lower-level chunk.
41
+ # It is a networking wrapper business to do interpret the result of #find.
42
+ #
43
+ # Insert is harder =) When new node level is higher than data chunk level
44
+ # we have to insert into proxy chunk and create all the levels of proxy
45
+ # chunks down to the data chunk. If node level is low, we just insert
46
+ # node into appropriate data chunk.
47
+ # The hard part about it are locking issues during insertion.
48
+ #
49
+ #
50
+ class ChunkedSkiplist
51
+ attr_accessor :lo_level, :hi_level, :probability, :container
52
+
53
+ DEFAULT_MAXLEVEL = 7
54
+ DEFAULT_PROBABILITY = 1/Math::E
55
+
56
+ def initialize(lo_level = nil, hi_level = nil, probability = nil, container = nil)
57
+ @lo_level = lo_level || 0
58
+ @hi_level = hi_level || DEFAULT_MAXLEVEL
59
+ @probability = probability || DEFAULT_PROBABILITY
60
+ @container = container || SimpleSkiplist.new(nil,
61
+ :maxlevel => @hi_level + 1, :probability => @probability)
62
+ end
63
+
64
+ # If chunk is not a lowest-level list, then it
65
+ # contains references to other chunks. Hence, it is a "proxy".
66
+ #
67
+ def proxy?
68
+ @lo_level > 0
69
+ end
70
+
71
+ # Insertion cases:
72
+ #
73
+ # |
74
+ # [ levels 16..23 ] | |
75
+ # [ levels 08..15 ] | | |
76
+ # [ levels 00..07 ] | | | |
77
+ # A B C D
78
+ #
79
+ # A - insert in a lower-level chunk
80
+ # B - insert in a 08..15-levels chunk, create new 0..7-level chunk
81
+ # C - insert in a 16..23-levels chunk, create new chunks of levels
82
+ # 0..7 and 8..15.
83
+ # D - create new 24..31-levels chunk with reference to previous head.
84
+ #
85
+ def insert(key, value, __level = nil)
86
+ @container.insert(key, value, __level)
87
+ end
88
+
89
+ # Create new chunk, move local skiplist there,
90
+ # create new skiplist here and insert
91
+ def promote_level(key, level, size)
92
+
93
+ end
94
+
95
+ def generate_chain(key, value, size, start_level)
96
+
97
+ end
98
+
99
+ # Finds reference to another chunk (if proxy) or an actual data.
100
+ #
101
+ def find(key)
102
+ proxy? ? @container.find_nearest(key) : @container.find(key)
103
+ end
104
+
105
+ # Generates random level of arbitrary size.
106
+ # In other words, it actually contains an infinite loop.
107
+ def random_level
108
+ p = @probability
109
+ l = 1
110
+ l += 1 while rand < p
111
+ return l
112
+ end
113
+ end
114
+ end
115
+
116
+ if __FILE__ == $0
117
+ require File.expand_path(File.dirname(__FILE__) + '/../data_structures/simple_skiplist.rb')
118
+ require 'benchmark'
119
+
120
+
121
+
122
+
123
+ end
@@ -0,0 +1,350 @@
1
+ require 'thread'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../util/class_optimization')
3
+
4
+ module StrokeDB
5
+ # Implements a thread-safe skiplist structure.
6
+ # Doesn't yield new skiplists
7
+ class SimpleSkiplist
8
+ include Enumerable
9
+
10
+ DEFAULT_MAXLEVEL = 32
11
+ DEFAULT_PROBABILITY = 1/Math::E
12
+
13
+ attr_accessor :maxlevel, :probability
14
+
15
+ def initialize(raw_list = nil, options = {})
16
+ @maxlevel = options[:maxlevel] || DEFAULT_MAXLEVEL
17
+ @probability = options[:probability] || DEFAULT_PROBABILITY
18
+ @head = raw_list && unserialize_list!(raw_list) || new_head
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ # Marshal API
23
+ def marshal_dump
24
+ raw_list = serialize_list(@head)
25
+ {
26
+ :options => {
27
+ :maxlevel => @maxlevel,
28
+ :probability => @probability
29
+ },
30
+ :raw_list => raw_list
31
+ }
32
+ end
33
+
34
+ def marshal_load(dumped)
35
+ initialize(dumped[:raw_list], dumped[:options])
36
+ self
37
+ end
38
+
39
+ # Tests whether skiplist is empty.
40
+ #
41
+ def empty?
42
+ !node_next(@head, 0)
43
+ end
44
+
45
+ # First key of a non-empty skiplist (nil for empty one)
46
+ #
47
+ def first_key
48
+ first = node_next(@head, 0)
49
+ return first ? first[1] : nil
50
+ end
51
+
52
+ # Insert a key-value pair. If key already exists,
53
+ # value will be overwritten.
54
+ #
55
+ def insert(key, value, __level = nil)
56
+ @mutex.synchronize do
57
+ newlevel = __level || random_level
58
+ x = node_first
59
+ level = node_level(x)
60
+ update = Array.new(level)
61
+ x = find_with_update(x, level, key, update)
62
+
63
+ # rewrite existing key
64
+ if node_compare(x, key) == 0
65
+ node_set_value!(x, value)
66
+ # insert in a middle
67
+ else
68
+ level = newlevel
69
+ newx = new_node(newlevel, key, value)
70
+ while level > 0
71
+ level -= 1
72
+ node_insert_after!(newx, update[level], level)
73
+ end
74
+ end
75
+ end
76
+ self
77
+ end
78
+
79
+ def find_with_update(x, level, key, update) #:nodoc:
80
+ while level > 0
81
+ level -= 1
82
+ xnext = node_next(x, level)
83
+ while node_compare(xnext, key) < 0
84
+ x = xnext
85
+ xnext = node_next(x, level)
86
+ end
87
+ update[level] = x
88
+ end
89
+ xnext
90
+ end
91
+
92
+ # Find is thread-safe and requires no mutexes locking.
93
+ def find_nearest_node(key) #:nodoc:
94
+ x = node_first
95
+ level = node_level(x)
96
+ while level > 0
97
+ level -= 1
98
+ xnext = node_next(x, level)
99
+ while node_compare(xnext, key) <= 0
100
+ x = xnext
101
+ xnext = node_next(x, level)
102
+ end
103
+ end
104
+ x
105
+ end
106
+
107
+ declare_optimized_methods(:Java) do
108
+ # Temporary off due to:
109
+ # ./vendor/java_inline.rb:19: cannot load Java class javax.tools.ToolProvider (NameError)
110
+ #
111
+ # require 'vendor/java_inline'
112
+ # inline(:Java) do |builder|
113
+ # builder.package "org.jruby.strokedb"
114
+ # builder.import "java.lang.reflect.*"
115
+ # builder.java %{
116
+ # public static Object find_Java(String key)
117
+ # {
118
+ # Object o = new Object();
119
+ # return o;
120
+ # /*Class[] param_types = new Class[1];
121
+ # param_types[0] = String;
122
+ # Method method = this.getClass().getMethod("find", param_types);
123
+ # Object[] invokeParam = new Object[1];
124
+ # invokeParam[0] = key;
125
+ #
126
+ # return method.invoke(this, invokeParam);
127
+ # */
128
+ # }
129
+ # }
130
+ # end
131
+ end
132
+
133
+ declare_optimized_methods(:C, :find_nearest_node, :find_with_update) do
134
+ require 'rubygems'
135
+ require 'inline'
136
+ inline(:C) do |builder|
137
+ builder.prefix %{
138
+ static ID i_node_first, i_node_level;
139
+ #define SS_NODE_NEXT(x, level) (rb_ary_entry(rb_ary_entry(x, 0), level))
140
+ static int ss_node_compare(VALUE x, VALUE key)
141
+ {
142
+ if (x == Qnil) return 1; /* tail */
143
+ VALUE key1 = rb_ary_entry(x, 1);
144
+ if (key1 == Qnil) return -1; /* head */
145
+ return rb_str_cmp(key1, key);
146
+ }
147
+ }
148
+ builder.add_to_init %{
149
+ i_node_first = rb_intern("node_first");
150
+ i_node_level = rb_intern("node_level");
151
+ }
152
+ builder.c %{
153
+ VALUE find_nearest_node_C(VALUE key)
154
+ {
155
+ VALUE x = rb_funcall(self, i_node_first, 0);
156
+ long level = FIX2LONG(rb_funcall(self, i_node_level, 1, x));
157
+ VALUE xnext;
158
+ while (level-- > 0)
159
+ {
160
+ xnext = SS_NODE_NEXT(x, level);
161
+ while (ss_node_compare(xnext, key) <= 0)
162
+ {
163
+ x = xnext;
164
+ xnext = SS_NODE_NEXT(x, level);
165
+ }
166
+ }
167
+ return x;
168
+ }
169
+ }
170
+ builder.c %{
171
+ static VALUE find_with_update_C(VALUE x, VALUE rlevel, VALUE key, VALUE update)
172
+ {
173
+ long level = FIX2LONG(rlevel);
174
+ VALUE xnext;
175
+ while (level-- > 0)
176
+ {
177
+ xnext = SS_NODE_NEXT(x, level);
178
+ while (ss_node_compare(xnext, key) < 0)
179
+ {
180
+ x = xnext;
181
+ xnext = SS_NODE_NEXT(x, level);
182
+ }
183
+ rb_ary_store(update, level, x);
184
+ }
185
+ return xnext;
186
+ }
187
+ }
188
+ end
189
+ end
190
+
191
+ # Finds a value with a nearest key to given key (from the left).
192
+ # For a set of keys [b, d, f], query "a" will return nil and query "c"
193
+ # will return a value under "b" key.
194
+ #
195
+ def find_nearest(key)
196
+ node_value(find_nearest_node(key))
197
+ end
198
+
199
+ # Returns value, associated with key. nil if key is not found.
200
+ #
201
+ def find(key)
202
+ x = find_nearest_node(key)
203
+ return node_value(x) if node_compare(x, key) == 0
204
+ nil # nothing found
205
+ end
206
+
207
+ def each_node #:nodoc:
208
+ x = node_next(node_first, 0)
209
+ while x
210
+ yield(x)
211
+ x = node_next(x, 0)
212
+ end
213
+ self
214
+ end
215
+
216
+ # Iterates over skiplist kay-value pairs
217
+ #
218
+ def each
219
+ each_node do |node|
220
+ yield(node_key(node), node_value(node))
221
+ end
222
+ end
223
+
224
+ # Constructs a skiplist from a hash values.
225
+ #
226
+ def self.from_hash(hash, options = {})
227
+ from_a(hash.to_a, options)
228
+ end
229
+
230
+ # Constructs a skiplist from an array of key-value tuples (arrays).
231
+ #
232
+ def self.from_a(ary, options = {})
233
+ sl = new(nil, options)
234
+ ary.each do |kv|
235
+ sl.insert(kv[0], kv[1])
236
+ end
237
+ sl
238
+ end
239
+
240
+ # Converts skiplist to an array of key-value pairs.
241
+ #
242
+ def to_a
243
+ inject([]) do |arr, pair|
244
+ arr << pair
245
+ arr
246
+ end
247
+ end
248
+
249
+ private
250
+
251
+ def serialize_list(head)
252
+ head = node_first.dup
253
+ head[0] = [ nil ] * node_level(head)
254
+ raw_list = [ head ]
255
+ prev_by_levels = [ head ] * node_level(head)
256
+ x = node_next(head, 0)
257
+ i = 1
258
+ while x
259
+ l = node_level(x)
260
+ nx = node_next(x, 0)
261
+ x = x.dup # make modification-safe copy of node
262
+ forwards = x[0]
263
+ while l > 0 # for each node level update forwards
264
+ l -= 1
265
+ prev_by_levels[l][l] = i # set raw_list's index as a forward ref
266
+ forwards[l] = nil # nullify forward pointer (point to tail)
267
+ prev_by_levels[l] = x # set in a previous stack
268
+ end
269
+ raw_list << x # store serialized node in an array
270
+ x = nx # step to next node
271
+ i += 1 # increment index in a raw_list array
272
+ end
273
+ raw_list
274
+ end
275
+
276
+ # Returns head of an imported skiplist.
277
+ # Caution: raw_list is modified (thus the bang).
278
+ # Pass dup-ed value if you need.
279
+ def unserialize_list!(raw_list)
280
+ x = raw_list[0]
281
+ while x != nil
282
+ forwards = x[0]
283
+ forwards.each_with_index do |rawindex, i|
284
+ forwards[i] = rawindex ? raw_list[rawindex] : nil
285
+ end
286
+ # go next node
287
+ x = forwards[0]
288
+ end
289
+ # return head
290
+ raw_list[0]
291
+ end
292
+
293
+ # C-style API for node operations
294
+ def node_first
295
+ @head
296
+ end
297
+
298
+ def node_level(x)
299
+ x[0].size
300
+ end
301
+
302
+ def node_next(x, level)
303
+ x[0][level]
304
+ end
305
+
306
+ def node_compare(x, key)
307
+ return 1 unless x # tail
308
+ return -1 unless x[1] # head
309
+ x[1] <=> key
310
+ end
311
+
312
+ def node_key(x)
313
+ x[1]
314
+ end
315
+
316
+ def node_value(x)
317
+ x[2]
318
+ end
319
+
320
+ def node_set_value!(x, value)
321
+ x[2] = value
322
+ end
323
+
324
+ def node_insert_after!(x, prev, level)
325
+ x[0][level] = prev[0][level]
326
+ prev[0][level] = x
327
+ end
328
+
329
+ def new_node(level, key, value)
330
+ [
331
+ [nil]*level,
332
+ key,
333
+ value
334
+ ]
335
+ end
336
+
337
+ def new_head
338
+ new_node(@maxlevel, nil, nil)
339
+ end
340
+
341
+ def random_level
342
+ p = @probability
343
+ m = @maxlevel
344
+ l = 1
345
+ l += 1 while rand < p && l < m
346
+ return l
347
+ end
348
+
349
+ end
350
+ end