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.
- data/README +18 -20
- data/bench.html +4001 -0
- data/bin/strokedb +14 -0
- data/examples/movies.rb +105 -0
- data/examples/movies2.rb +97 -0
- data/examples/strokewiki/README +28 -0
- data/examples/strokewiki/view/edit.xhtml +27 -0
- data/examples/strokewiki/view/new.xhtml +26 -0
- data/examples/strokewiki/view/pages.xhtml +27 -0
- data/examples/strokewiki/view/show.xhtml +40 -0
- data/examples/strokewiki/view/versions.xhtml +25 -0
- data/examples/strokewiki/wiki.rb +106 -0
- data/examples/todo.rb +92 -0
- data/lib/strokedb.rb +85 -0
- data/lib/{config → strokedb}/config.rb +14 -9
- data/lib/strokedb/console.rb +87 -0
- data/lib/strokedb/core_ext.rb +10 -0
- data/lib/{util/ext → strokedb/core_ext}/blank.rb +1 -1
- data/lib/{util/ext → strokedb/core_ext}/enumerable.rb +0 -0
- data/lib/{util/ext → strokedb/core_ext}/fixnum.rb +0 -0
- data/lib/strokedb/core_ext/float.rb +4 -0
- data/lib/{util/ext → strokedb/core_ext}/hash.rb +0 -0
- data/lib/strokedb/core_ext/infinity.rb +33 -0
- data/lib/strokedb/core_ext/kernel.rb +41 -0
- data/lib/strokedb/core_ext/object.rb +16 -0
- data/lib/{util/ext → strokedb/core_ext}/string.rb +28 -1
- data/lib/strokedb/core_ext/symbol.rb +13 -0
- data/lib/strokedb/data_structures.rb +5 -0
- data/lib/strokedb/data_structures/chunked_skiplist.rb +123 -0
- data/lib/{data_structures → strokedb/data_structures}/inverted_list.rb +0 -0
- data/lib/{data_structures → strokedb/data_structures}/point_query.rb +0 -0
- data/lib/strokedb/data_structures/simple_skiplist.rb +350 -0
- data/lib/{data_structures → strokedb/data_structures}/skiplist.rb +1 -1
- data/lib/{document → strokedb}/document.rb +180 -71
- data/lib/{document → strokedb/document}/callback.rb +0 -0
- data/lib/{document → strokedb/document}/delete.rb +2 -2
- data/lib/strokedb/document/dsl.rb +4 -0
- data/lib/{document → strokedb/document/dsl}/associations.rb +0 -0
- data/lib/{document → strokedb/document/dsl}/coercions.rb +0 -0
- data/lib/strokedb/document/dsl/meta_dsl.rb +7 -0
- data/lib/{document → strokedb/document/dsl}/validations.rb +26 -21
- data/lib/{document → strokedb/document/dsl}/virtualize.rb +0 -0
- data/lib/{document → strokedb/document}/meta.rb +92 -29
- data/lib/{document → strokedb/document}/slot.rb +17 -5
- data/lib/{document → strokedb/document}/util.rb +0 -0
- data/lib/{document → strokedb/document}/versions.rb +2 -2
- data/lib/strokedb/index.rb +2 -0
- data/lib/strokedb/nsurl.rb +24 -0
- data/lib/strokedb/store.rb +149 -0
- data/lib/strokedb/stores.rb +6 -0
- data/lib/{stores → strokedb/stores}/chainable_storage.rb +20 -14
- data/lib/strokedb/stores/file_storage.rb +118 -0
- data/lib/{stores/inverted_list_index → strokedb/stores}/inverted_list_file_storage.rb +50 -0
- data/lib/strokedb/stores/memory_storage.rb +80 -0
- data/lib/{stores → strokedb/stores}/remote_store.rb +10 -4
- data/lib/strokedb/sync.rb +4 -0
- data/lib/{sync → strokedb/sync}/chain_sync.rb +0 -0
- data/lib/{sync → strokedb/sync}/diff.rb +12 -1
- data/lib/{sync/stroke_diff → strokedb/sync/diff}/array.rb +1 -1
- data/lib/{sync/stroke_diff → strokedb/sync/diff}/default.rb +0 -0
- data/lib/{sync/stroke_diff → strokedb/sync/diff}/hash.rb +1 -1
- data/lib/{sync/stroke_diff → strokedb/sync/diff}/string.rb +1 -1
- data/lib/{sync → strokedb/sync}/lamport_timestamp.rb +0 -0
- data/lib/{sync → strokedb/sync}/store_sync.rb +15 -7
- data/lib/strokedb/transaction.rb +78 -0
- data/lib/{util → strokedb}/util.rb +14 -7
- data/lib/strokedb/util/attach_dsl.rb +29 -0
- data/lib/{util → strokedb/util}/blankslate.rb +0 -0
- data/lib/strokedb/util/class_optimization.rb +93 -0
- data/lib/{util → strokedb/util}/inflect.rb +0 -0
- data/lib/strokedb/util/java_util.rb +13 -0
- data/lib/{util → strokedb/util}/lazy_array.rb +0 -0
- data/lib/{util → strokedb/util}/lazy_mapping_array.rb +4 -0
- data/lib/{util → strokedb/util}/lazy_mapping_hash.rb +0 -0
- data/lib/{util → strokedb/util}/serialization.rb +21 -0
- data/lib/strokedb/util/uuid.rb +159 -0
- data/lib/{util → strokedb/util}/xml.rb +0 -0
- data/lib/{view → strokedb}/view.rb +2 -2
- data/lib/strokedb/volumes.rb +5 -0
- data/lib/strokedb/volumes/archive_volume.rb +165 -0
- data/lib/strokedb/volumes/block_volume.rb +169 -0
- data/lib/strokedb/volumes/distributed_pointer.rb +43 -0
- data/lib/strokedb/volumes/fixed_length_skiplist_volume.rb +109 -0
- data/lib/strokedb/volumes/map_volume.rb +268 -0
- data/meta/MANIFEST +175 -0
- data/script/console +2 -70
- data/spec/integration/remote_store_spec.rb +70 -0
- data/spec/integration/search_spec.rb +76 -0
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/lib/spec_helper.rb +1 -0
- data/spec/lib/strokedb/config_spec.rb +250 -0
- data/spec/lib/strokedb/core_ext/blank_spec.rb +20 -0
- data/spec/lib/strokedb/core_ext/extract_spec.rb +42 -0
- data/spec/lib/strokedb/core_ext/float_spec.rb +62 -0
- data/spec/lib/strokedb/core_ext/infinity_spec.rb +40 -0
- data/spec/lib/strokedb/core_ext/spec_helper.rb +1 -0
- data/spec/lib/strokedb/core_ext/string_spec.rb +25 -0
- data/spec/lib/strokedb/core_ext/symbol_spec.rb +8 -0
- data/spec/lib/strokedb/data_structures/chunked_skiplist_spec.rb +144 -0
- data/spec/lib/strokedb/data_structures/inverted_list_spec.rb +172 -0
- data/spec/lib/strokedb/data_structures/simple_skiplist_spec.rb +200 -0
- data/spec/lib/strokedb/data_structures/skiplist_spec.rb +253 -0
- data/spec/lib/strokedb/data_structures/spec_helper.rb +1 -0
- data/spec/lib/strokedb/document/associations_spec.rb +319 -0
- data/spec/lib/strokedb/document/callbacks_spec.rb +134 -0
- data/spec/lib/strokedb/document/coercions_spec.rb +110 -0
- data/spec/lib/strokedb/document/document_spec.rb +1063 -0
- data/spec/lib/strokedb/document/meta_meta_spec.rb +30 -0
- data/spec/lib/strokedb/document/meta_spec.rb +435 -0
- data/spec/lib/strokedb/document/metaslot_spec.rb +43 -0
- data/spec/lib/strokedb/document/slot_spec.rb +130 -0
- data/spec/lib/strokedb/document/spec_helper.rb +1 -0
- data/spec/lib/strokedb/document/validations_spec.rb +1081 -0
- data/spec/lib/strokedb/document/virtualize_spec.rb +80 -0
- data/spec/lib/strokedb/nsurl_spec.rb +73 -0
- data/spec/lib/strokedb/spec_helper.rb +1 -0
- data/spec/lib/strokedb/stores/chained_storages_spec.rb +116 -0
- data/spec/lib/strokedb/stores/spec_helper.rb +1 -0
- data/spec/lib/strokedb/stores/store_spec.rb +201 -0
- data/spec/lib/strokedb/stores/transaction_spec.rb +107 -0
- data/spec/lib/strokedb/sync/chain_sync_spec.rb +43 -0
- data/spec/lib/strokedb/sync/diff_spec.rb +111 -0
- data/spec/lib/strokedb/sync/lamport_timestamp_spec.rb +174 -0
- data/spec/lib/strokedb/sync/slot_diff_spec.rb +164 -0
- data/spec/lib/strokedb/sync/spec_helper.rb +1 -0
- data/spec/lib/strokedb/sync/store_sync_spec.rb +181 -0
- data/spec/lib/strokedb/sync/stroke_diff/array_spec.rb +97 -0
- data/spec/lib/strokedb/sync/stroke_diff/complex_spec.rb +58 -0
- data/spec/lib/strokedb/sync/stroke_diff/hash_spec.rb +144 -0
- data/spec/lib/strokedb/sync/stroke_diff/scalar_spec.rb +23 -0
- data/spec/lib/strokedb/sync/stroke_diff/spec_helper.rb +25 -0
- data/spec/lib/strokedb/sync/stroke_diff/string_spec.rb +61 -0
- data/spec/lib/strokedb/util/attach_dsl_spec.rb +45 -0
- data/spec/lib/strokedb/util/inflect_spec.rb +14 -0
- data/spec/lib/strokedb/util/lazy_array_spec.rb +157 -0
- data/spec/lib/strokedb/util/lazy_mapping_array_spec.rb +174 -0
- data/spec/lib/strokedb/util/lazy_mapping_hash_spec.rb +92 -0
- data/spec/lib/strokedb/util/spec_helper.rb +1 -0
- data/spec/lib/strokedb/util/uuid_spec.rb +46 -0
- data/spec/lib/strokedb/view_spec.rb +228 -0
- data/spec/lib/strokedb/volumes/archive_volume_spec.rb +105 -0
- data/spec/lib/strokedb/volumes/block_volume_spec.rb +100 -0
- data/spec/lib/strokedb/volumes/distributed_pointer_spec.rb +14 -0
- data/spec/lib/strokedb/volumes/fixed_length_skiplist_volume_spec.rb +177 -0
- data/spec/lib/strokedb/volumes/map_volume_spec.rb +172 -0
- data/spec/lib/strokedb/volumes/spec_helper.rb +1 -0
- data/spec/regression/docref_spec.rb +94 -0
- data/spec/regression/meta_spec.rb +23 -0
- data/spec/regression/spec_helper.rb +1 -0
- data/spec/regression/sync_spec.rb +36 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/temp/storages/TIMESTAMP +1 -0
- data/spec/temp/storages/UUID +1 -0
- data/spec/temp/storages/database-sync/TIMESTAMP +1 -0
- data/spec/temp/storages/database-sync/UUID +1 -0
- data/spec/temp/storages/database-sync/config +1 -0
- data/spec/temp/storages/database-sync/file/LAST +1 -0
- data/spec/temp/storages/database-sync/file/bd/f6/bdf675e5-8a7b-494e-97f2-f74a14ccd95d.av +0 -0
- data/spec/temp/storages/database-sync/file/uindex.wal +0 -0
- data/spec/temp/storages/database-sync/inverted_list_file/INVERTED_INDEX +1 -0
- data/spec/temp/storages/inverted_list_storage/INVERTED_INDEX +0 -0
- data/strokedb.gemspec +120 -0
- data/task/benchmark.task +9 -0
- data/task/ditz.task +30 -0
- data/task/echoe.rb +17 -0
- data/task/rcov.task +50 -0
- data/task/rdoc.task +10 -0
- data/task/rspec.task +0 -0
- data/vendor/java_inline.rb +106 -0
- data/vendor/rbmodexcl/mrimodexcl.rb +82 -0
- data/vendor/rbmodexcl/rbmodexcl.rb +5 -0
- data/vendor/rbmodexcl/rbxmodexcl.rb +48 -0
- data/vendor/rbmodexcl/spec/unextend_spec.rb +50 -0
- data/vendor/rbmodexcl/spec/uninclude_spec.rb +26 -0
- metadata +271 -79
- data/CONTRIBUTORS +0 -7
- data/CREDITS +0 -13
- data/bin/sdbc +0 -2
- data/lib/init.rb +0 -57
- data/lib/stores/inverted_list_index/inverted_list_index.rb +0 -49
- data/lib/stores/skiplist_store/chunk.rb +0 -119
- data/lib/stores/skiplist_store/chunk_storage.rb +0 -21
- data/lib/stores/skiplist_store/file_chunk_storage.rb +0 -44
- data/lib/stores/skiplist_store/memory_chunk_storage.rb +0 -37
- data/lib/stores/skiplist_store/skiplist_store.rb +0 -217
- data/lib/stores/store.rb +0 -5
- data/lib/sync/stroke_diff/stroke_diff.rb +0 -9
- data/lib/util/ext/object.rb +0 -8
- data/lib/util/java_util.rb +0 -9
- data/lib/util/trigger_partition.rb +0 -136
- 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,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
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|