mongoose 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +51 -1
- data/changes.txt +11 -0
- data/example/simple_examples.rb +47 -43
- data/lib/mongoose.rb +1 -3
- data/lib/mongoose/column.rb +10 -38
- data/lib/mongoose/database.rb +4 -13
- data/lib/mongoose/skiplist.rb +407 -336
- data/lib/mongoose/table.rb +53 -43
- data/lib/mongoose/util.rb +1 -1
- data/test/tc_relations.rb +139 -0
- data/test/tc_table.rb +51 -5
- data/test/ts_mongoose.rb +1 -0
- metadata +3 -2
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Mongoose 0.1.
|
1
|
+
= Mongoose 0.1.1
|
2
2
|
|
3
3
|
A database management system written in Ruby. It has an ActiveRecord-like
|
4
4
|
interface, uses Skiplists for its indexing, and Marshal for its data
|
@@ -35,6 +35,7 @@ Unpack the file you downloaded. Execute "ruby setup.rb".
|
|
35
35
|
can greatly reduce startup times.
|
36
36
|
* Supports any datatype that Marshal supports.
|
37
37
|
* Table relations supported via has_one, has_many.
|
38
|
+
* Fast, Fast, Fast!
|
38
39
|
|
39
40
|
|
40
41
|
== Documentation
|
@@ -88,6 +89,9 @@ end
|
|
88
89
|
|
89
90
|
# Delete a record.
|
90
91
|
Plane.find(1).destroy
|
92
|
+
|
93
|
+
# Close database.
|
94
|
+
db.close
|
91
95
|
|
92
96
|
== Manifest
|
93
97
|
|
@@ -134,3 +138,49 @@ jcribbs@netpromi.com
|
|
134
138
|
== Home Page
|
135
139
|
|
136
140
|
http://rubyforge.org/projects/mongoose/
|
141
|
+
|
142
|
+
|
143
|
+
== Why Did I Develop Mongoose?
|
144
|
+
|
145
|
+
Well, I started to look into performance improvements for KirbyBase. One thing
|
146
|
+
I noticed was that Ruby takes a comparatively long time converting strings to
|
147
|
+
native data types like Integer, Time, etc. Since KirbyBase stores its records
|
148
|
+
as strings, returning a large result set could take a long time. I found that
|
149
|
+
if I Marshaled records before I wrote them to disk, a subsequent read of those
|
150
|
+
records was significantly faster.
|
151
|
+
|
152
|
+
About the same time, I read a paper about skiplists. A skiplist is a data
|
153
|
+
structure that is relatively simple (compared to say a b-tree) to understand
|
154
|
+
and implement in code. I was able to take the pseudo-code in the paper and
|
155
|
+
implement a Ruby version in a couple of hours. Skiplists are pretty fast and
|
156
|
+
since they are pretty easy to understand, I think there is good potential to
|
157
|
+
tweak them. So, I wanted to try using skiplists for indexing rather than
|
158
|
+
KirbyBase's array-based indexes.
|
159
|
+
|
160
|
+
I started to retrofit both the Marshal serialization and Skiplists in KirbyBase,
|
161
|
+
but quickly found that it was going to be more work than just starting over from
|
162
|
+
scratch with a new design. Besides, I didn't want to radically change KirbyBase
|
163
|
+
and piss off the current user base (both of you know who you are).
|
164
|
+
|
165
|
+
So, I started from scratch. This also gave me the opportunity to make two other
|
166
|
+
major changes. First of all, I wanted to keep the query language as close to
|
167
|
+
KirbyBase's as possible (i.e. Ruby blocks), but I wanted more control over the
|
168
|
+
query expression. I took the opportunity to borrow a lot of idea's from Ezra's
|
169
|
+
ez_where plugin. I think this will give me the capability down the road to
|
170
|
+
tweak the query engine, based on the query itself.
|
171
|
+
|
172
|
+
The second thing I changed was that I have finally seen the light about
|
173
|
+
ActiveRecord, so I stole pretty much all of Logan's KirbyRecord code to give
|
174
|
+
Mongoose an ActiveRecord-like api.
|
175
|
+
|
176
|
+
The end result of all of this (I hope) is a database management system that is
|
177
|
+
small, easy to use, and fast.
|
178
|
+
|
179
|
+
|
180
|
+
== What about KirbyBase?
|
181
|
+
|
182
|
+
KirbyBase is not going anywhere. I still plan on supporting it into the
|
183
|
+
foreseeable future. However, I imagine that most new development will be
|
184
|
+
directed at Mongoose.
|
185
|
+
|
186
|
+
|
data/changes.txt
CHANGED
@@ -1,2 +1,13 @@
|
|
1
1
|
2006-07-19:: Version 0.1.0
|
2
2
|
* Initial release.
|
3
|
+
|
4
|
+
2006-07-20:: Version 0.1.1
|
5
|
+
* Fixed bug where saving an updated record that was not the same size as the
|
6
|
+
original was not working.
|
7
|
+
* Removed in-memory tracking of last_id_used and deleted_records_counter. Now
|
8
|
+
I just call out to the header file when I need to get a new id number, etc.
|
9
|
+
* Added #dump_to_hash and #load_from_hash methods to SkipList class. This has
|
10
|
+
cut index initialization times in half, increased query speed, and reduced
|
11
|
+
memory usage.
|
12
|
+
* Cleaned up the code in SkipList class.
|
13
|
+
* Added more unit tests.
|
data/example/simple_examples.rb
CHANGED
@@ -5,9 +5,11 @@ rescue LoadError
|
|
5
5
|
require 'mongoose'
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
unless ARGV[0] == 'keep-data'
|
9
|
+
puts 'Deleting old tables!'
|
10
|
+
Dir.glob('[plane|pilot|flight]*.mg?').each do |filename|
|
11
|
+
File.delete(filename)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
|
13
15
|
class Plane < Mongoose::Table
|
@@ -18,50 +20,52 @@ end
|
|
18
20
|
db = Mongoose::Database.new
|
19
21
|
|
20
22
|
# Create new table. Notice how you specify whether a column is indexed or not.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
unless ARGV[0] == 'keep-data'
|
24
|
+
db.create_table(:plane) do |tbl|
|
25
|
+
tbl.add_indexed_column(:plane_name, :string)
|
26
|
+
tbl.add_column(:country, :string)
|
27
|
+
tbl.add_indexed_column(:speed, :integer)
|
28
|
+
tbl.add_column(:range, :integer)
|
29
|
+
end
|
27
30
|
|
28
|
-
# Add records.
|
29
|
-
rec = Plane.new
|
30
|
-
rec.plane_name = 'P-51'
|
31
|
-
rec.country = 'USA'
|
32
|
-
rec.speed = 402
|
33
|
-
rec.range = 1205
|
34
|
-
rec.save
|
35
|
-
|
36
|
-
rec2 = Plane.new
|
37
|
-
rec2.plane_name = 'Spitfire'
|
38
|
-
rec2.country = 'Great Britain'
|
39
|
-
rec2.speed = 333
|
40
|
-
rec2.range = 454
|
41
|
-
rec2.save
|
42
|
-
|
43
|
-
rec3 = Plane.new
|
44
|
-
rec3.plane_name = 'ME-109'
|
45
|
-
rec3.country = 'Germany'
|
46
|
-
rec3.speed = 354
|
47
|
-
rec3.range = 501
|
48
|
-
rec3.save
|
49
|
-
|
50
|
-
rec4 = Plane.new
|
51
|
-
rec4.plane_name = 'P-39'
|
52
|
-
rec4.country = 'USA'
|
53
|
-
rec4.range = 701
|
54
|
-
|
55
|
-
# Forgot value for speed, which is a required field.
|
56
|
-
begin
|
31
|
+
# Add records.
|
32
|
+
rec = Plane.new
|
33
|
+
rec.plane_name = 'P-51'
|
34
|
+
rec.country = 'USA'
|
35
|
+
rec.speed = 402
|
36
|
+
rec.range = 1205
|
37
|
+
rec.save
|
38
|
+
|
39
|
+
rec2 = Plane.new
|
40
|
+
rec2.plane_name = 'Spitfire'
|
41
|
+
rec2.country = 'Great Britain'
|
42
|
+
rec2.speed = 333
|
43
|
+
rec2.range = 454
|
44
|
+
rec2.save
|
45
|
+
|
46
|
+
rec3 = Plane.new
|
47
|
+
rec3.plane_name = 'ME-109'
|
48
|
+
rec3.country = 'Germany'
|
49
|
+
rec3.speed = 354
|
50
|
+
rec3.range = 501
|
51
|
+
rec3.save
|
52
|
+
|
53
|
+
rec4 = Plane.new
|
54
|
+
rec4.plane_name = 'P-39'
|
55
|
+
rec4.country = 'USA'
|
56
|
+
rec4.range = 701
|
57
|
+
|
58
|
+
# Forgot value for speed, which is a required field.
|
59
|
+
begin
|
60
|
+
rec4.save
|
61
|
+
rescue RuntimeError => e
|
62
|
+
puts e
|
63
|
+
end
|
64
|
+
|
65
|
+
rec4.speed = 339
|
57
66
|
rec4.save
|
58
|
-
rescue RuntimeError => e
|
59
|
-
puts e
|
60
67
|
end
|
61
68
|
|
62
|
-
rec4.speed = 339
|
63
|
-
rec4.save
|
64
|
-
|
65
69
|
puts "\n\nFind P-51 record by ID"
|
66
70
|
p_51 = Plane.find(1)
|
67
71
|
p p_51
|
data/lib/mongoose.rb
CHANGED
@@ -18,13 +18,11 @@ require 'mongoose/util'
|
|
18
18
|
# Homepage:: http://rubyforge.org/projects/mongoose/
|
19
19
|
# Copyright:: Copyright (c) 2006 NetPro Technologies, LLC
|
20
20
|
# License:: Distributed under the same terms as Ruby
|
21
|
-
# History:
|
22
|
-
# 2006-07-19:: Version 0.1.0
|
23
|
-
# * Initial release.
|
24
21
|
#
|
25
22
|
#
|
26
23
|
module Mongoose
|
27
24
|
|
25
|
+
VERSION = '0.1.1'
|
28
26
|
DATA_TYPES = [:string, :integer, :float, :time, :date, :datetime, :boolean]
|
29
27
|
TBL_EXT = '.mgt'
|
30
28
|
TBL_HDR_EXT = '.mgh'
|
data/lib/mongoose/column.rb
CHANGED
@@ -50,12 +50,6 @@ class Column < BaseColumn
|
|
50
50
|
@idx = LinearSearch.new(self)
|
51
51
|
end
|
52
52
|
|
53
|
-
def store(key, value)
|
54
|
-
end
|
55
|
-
|
56
|
-
def remove(key, value)
|
57
|
-
end
|
58
|
-
|
59
53
|
def >(other)
|
60
54
|
@tbl_class.query << [@idx, :>, other]
|
61
55
|
end
|
@@ -110,6 +104,9 @@ class IndexedColumn < BaseColumn
|
|
110
104
|
rebuild_index_file if index_file_out_of_date?
|
111
105
|
end
|
112
106
|
|
107
|
+
#-----------------------------------------------------------------------------
|
108
|
+
# init_index
|
109
|
+
#-----------------------------------------------------------------------------
|
113
110
|
def init_index
|
114
111
|
if File.exists?(@index_file_name) and not index_file_out_of_date?
|
115
112
|
rebuild_index_from_index_file
|
@@ -149,12 +146,12 @@ end
|
|
149
146
|
#-------------------------------------------------------------------------------
|
150
147
|
class SkipListIndexColumn < IndexedColumn
|
151
148
|
def initialize(tbl_class, name, col_def)
|
152
|
-
@idx = SkipList.new
|
149
|
+
@idx = SkipList.new(self)
|
153
150
|
super
|
154
151
|
end
|
155
152
|
|
156
153
|
def clear_index
|
157
|
-
@idx = SkipList.new
|
154
|
+
@idx = SkipList.new(self)
|
158
155
|
end
|
159
156
|
|
160
157
|
def >(other)
|
@@ -190,9 +187,7 @@ class SkipListIndexColumn < IndexedColumn
|
|
190
187
|
end
|
191
188
|
|
192
189
|
def rebuild_index_file
|
193
|
-
with_index_file('w')
|
194
|
-
@idx.each { |k,v| fptr.write(Marshal.dump([k, v])) }
|
195
|
-
end
|
190
|
+
with_index_file('w') { |fptr| fptr.write(Marshal.dump(@idx.dump_to_hash)) }
|
196
191
|
end
|
197
192
|
|
198
193
|
def rebuild_index_from_table
|
@@ -206,15 +201,7 @@ class SkipListIndexColumn < IndexedColumn
|
|
206
201
|
|
207
202
|
def rebuild_index_from_index_file
|
208
203
|
clear_index
|
209
|
-
|
210
|
-
with_index_file do |fptr|
|
211
|
-
begin
|
212
|
-
while true
|
213
|
-
@idx.load(*Marshal.load(fptr))
|
214
|
-
end
|
215
|
-
rescue EOFError
|
216
|
-
end
|
217
|
-
end
|
204
|
+
with_index_file { |fptr| @idx.load_from_hash(Marshal.load(fptr)) }
|
218
205
|
end
|
219
206
|
|
220
207
|
def add_index_rec(key, value)
|
@@ -235,7 +222,6 @@ class IDColumn < IndexedColumn
|
|
235
222
|
|
236
223
|
def_delegator(:@idx, :[], :[])
|
237
224
|
def_delegator(:@idx, :keys, :keys)
|
238
|
-
# def_delegator(:@idx, :delete, :remove)
|
239
225
|
|
240
226
|
def initialize(tbl_class, name, col_def)
|
241
227
|
@idx = {}
|
@@ -247,31 +233,17 @@ class IDColumn < IndexedColumn
|
|
247
233
|
end
|
248
234
|
|
249
235
|
def rebuild_index_file
|
250
|
-
with_index_file('w')
|
251
|
-
# @idx.each_pair { |k,v| fptr.write(Marshal.dump([k, v])) }
|
252
|
-
fptr.write(Marshal.dump(@idx))
|
253
|
-
end
|
236
|
+
with_index_file('w') { |fptr| fptr.write(Marshal.dump(@idx)) }
|
254
237
|
end
|
255
238
|
|
256
239
|
def rebuild_index_from_table
|
257
240
|
clear_index
|
258
|
-
@tbl_class.get_all_recs
|
259
|
-
add_index_rec(rec[0], fpos)
|
260
|
-
end
|
241
|
+
@tbl_class.get_all_recs { |rec, fpos| add_index_rec(rec[0], fpos) }
|
261
242
|
end
|
262
243
|
|
263
244
|
def rebuild_index_from_index_file
|
264
245
|
clear_index
|
265
|
-
|
266
|
-
with_index_file do |fptr|
|
267
|
-
# begin
|
268
|
-
# while true
|
269
|
-
# add_index_rec(*Marshal.load(fptr))
|
270
|
-
# end
|
271
|
-
# rescue EOFError
|
272
|
-
# end
|
273
|
-
@idx = Marshal.load(fptr)
|
274
|
-
end
|
246
|
+
with_index_file { |fptr| @idx = Marshal.load(fptr) }
|
275
247
|
end
|
276
248
|
|
277
249
|
def add_index_rec(id, fpos)
|
data/lib/mongoose/database.rb
CHANGED
@@ -37,7 +37,7 @@ class Database
|
|
37
37
|
def create_table(table_name)
|
38
38
|
raise "Table already exists!" if table_exists?(table_name)
|
39
39
|
|
40
|
-
class_name =
|
40
|
+
class_name = Util.us_case_to_class_case(table_name)
|
41
41
|
|
42
42
|
tbl_header = {}
|
43
43
|
tbl_header[:table_name] = table_name
|
@@ -64,7 +64,7 @@ class Database
|
|
64
64
|
# drop_table
|
65
65
|
#-----------------------------------------------------------------------------
|
66
66
|
def drop_table(table_name)
|
67
|
-
class_name =
|
67
|
+
class_name = Util.us_case_to_class_case(table_name)
|
68
68
|
|
69
69
|
@tables[Object.const_get(class_name)][:columns].each do |c|
|
70
70
|
if c.indexed?
|
@@ -92,22 +92,13 @@ class Database
|
|
92
92
|
# init_table
|
93
93
|
#-----------------------------------------------------------------------------
|
94
94
|
def init_table(table_name)
|
95
|
-
class_name =
|
95
|
+
class_name = Util.us_case_to_class_case(table_name)
|
96
96
|
|
97
97
|
@tables[Object.full_const_get(class_name)] = { :class_name => class_name,
|
98
|
-
:table_name => table_name, :columns => [], :query => []
|
99
|
-
:last_id_used => nil, :deleted_recs_counter => nil }
|
98
|
+
:table_name => table_name, :columns => [], :query => [] }
|
100
99
|
|
101
100
|
Object.full_const_get(class_name).init_table
|
102
101
|
end
|
103
|
-
|
104
|
-
#-----------------------------------------------------------------------------
|
105
|
-
# get_class_name_from_table_name
|
106
|
-
#-----------------------------------------------------------------------------
|
107
|
-
def get_class_name_from_table_name(table_name)
|
108
|
-
return table_name.to_s.split('_').inject('') { |full_name, word|
|
109
|
-
full_name + word.capitalize }
|
110
|
-
end
|
111
102
|
end
|
112
103
|
|
113
104
|
end
|
data/lib/mongoose/skiplist.rb
CHANGED
@@ -1,412 +1,483 @@
|
|
1
|
-
require 'pp'
|
2
|
-
|
3
1
|
module Mongoose
|
4
2
|
|
5
3
|
#---------------------------------------------------------------------------
|
6
4
|
# SkipList Class
|
7
5
|
#---------------------------------------------------------------------------
|
8
6
|
class SkipList
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
7
|
+
MAX_LEVEL = 31
|
8
|
+
OPTIMAL_PROBABILITY = 0.25
|
9
|
+
|
10
|
+
attr_reader :size
|
11
|
+
|
12
|
+
#-----------------------------------------------------------------------
|
13
|
+
# initialize
|
14
|
+
#-----------------------------------------------------------------------
|
15
|
+
def initialize(col)
|
16
|
+
@col = col
|
17
|
+
@size = 0
|
18
|
+
|
19
|
+
# Create header and footer nodes.
|
20
|
+
@footer = FooterNode.new
|
21
|
+
@header = HeaderNode.new
|
22
|
+
|
23
|
+
# Point all header.forward references to footer node.
|
24
|
+
0.upto(MAX_LEVEL) { |i| @header.forward[i] = @footer }
|
25
|
+
|
26
|
+
# This attribute will hold the actual level of the skip list.
|
27
|
+
@level = 0
|
28
|
+
end
|
29
|
+
|
30
|
+
#-----------------------------------------------------------------------
|
31
|
+
# store
|
32
|
+
#-----------------------------------------------------------------------
|
33
|
+
#++
|
34
|
+
# If key not found, will insert new record, otherwise will update value
|
35
|
+
# of existing record.
|
36
|
+
#
|
37
|
+
def store(search_key, new_value)
|
38
|
+
# This array will be used to determine which records need their
|
39
|
+
# forward array re-adjusted after a new node is created.
|
40
|
+
update = []
|
41
|
+
|
42
|
+
# Start off at the header node.
|
43
|
+
x = @header
|
44
|
+
|
45
|
+
# Starting at the current highest level of the skip list, walk from
|
46
|
+
# left to right at that level, until you see that the next node's
|
47
|
+
# key is greater than the key you are searching for. When this
|
48
|
+
# happend, you want to add the current node you are on to the list
|
49
|
+
# of nodes that need to have their forward arrays updated. Next,
|
50
|
+
# you want to drop down a level on the current node and start
|
51
|
+
# walking forward again, until you again see that the next node's
|
52
|
+
# key is bigger than the search key. In this way, you are walking
|
53
|
+
# through the skip list, constantly moving to the right and moving
|
54
|
+
# down, until you reach level 0 and are on the node whose key is
|
55
|
+
# either equal to the search key (in which case an update will take
|
56
|
+
# place), or the highest key in the list that is still lower than
|
57
|
+
# the search key (in which case, you have found the place to do an
|
58
|
+
# insert).
|
59
|
+
@level.downto(0) do |i|
|
60
|
+
while x.forward[i].key < search_key
|
61
|
+
x = x.forward[i]
|
62
|
+
end
|
63
|
+
update[i] = x
|
33
64
|
end
|
34
65
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# of
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
while x.forward[i].key < search_key
|
66
|
-
x = x.forward[i]
|
67
|
-
end
|
68
|
-
update[i] = x
|
69
|
-
end
|
70
|
-
|
71
|
-
x = x.forward[0]
|
72
|
-
|
73
|
-
# If the search key was found, simply update the value of the node.
|
74
|
-
if x.key == search_key
|
75
|
-
x.value << new_value unless x.value.include?(new_value)
|
76
|
-
# If this is an insert, determine the number of levels it will have
|
77
|
-
# using a random number. This is what keeps the skip list balanced.
|
78
|
-
else
|
79
|
-
lvl = random_level
|
80
|
-
|
81
|
-
# If the new level is higher than the actual current level, we
|
82
|
-
# need to make sure that the header node gets updated at these
|
83
|
-
# levels. Then, we set the actual current level equal to the
|
84
|
-
# new level.
|
85
|
-
if lvl > @level
|
86
|
-
(@level + 1).upto(lvl) { |i| update[i] = @header }
|
87
|
-
@level = lvl
|
88
|
-
end
|
89
|
-
|
90
|
-
# Create a new node.
|
91
|
-
x = Node.new(lvl, search_key, [new_value])
|
92
|
-
|
93
|
-
# Now, we need to update all of the nodes that will be affected
|
94
|
-
# by the insertion of the new node. These are nodes whose
|
95
|
-
# forward array either will point to the new node and the new
|
96
|
-
# node itself.
|
97
|
-
0.upto(lvl) do |i|
|
98
|
-
x.forward[i] = update[i].forward[i]
|
99
|
-
update[i].forward[i] = x
|
100
|
-
end
|
66
|
+
x = x.forward[0]
|
67
|
+
|
68
|
+
# If the search key was found, simply update the value of the node.
|
69
|
+
if x.key == search_key
|
70
|
+
x.value << new_value unless x.value.include?(new_value)
|
71
|
+
# If this is an insert, determine the number of levels it will have
|
72
|
+
# using a random number. This is what keeps the skip list balanced.
|
73
|
+
else
|
74
|
+
lvl = random_level
|
75
|
+
|
76
|
+
# If the new level is higher than the actual current level, we
|
77
|
+
# need to make sure that the header node gets updated at these
|
78
|
+
# levels. Then, we set the actual current level equal to the
|
79
|
+
# new level.
|
80
|
+
if lvl > @level
|
81
|
+
(@level + 1).upto(lvl) { |i| update[i] = @header }
|
82
|
+
@level = lvl
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create a new node.
|
86
|
+
x = Node.new(lvl, search_key, [new_value])
|
87
|
+
|
88
|
+
# Now, we need to update all of the nodes that will be affected
|
89
|
+
# by the insertion of the new node. These are nodes whose
|
90
|
+
# forward array either will point to the new node and the new
|
91
|
+
# node itself.
|
92
|
+
0.upto(lvl) do |i|
|
93
|
+
x.forward[i] = update[i].forward[i]
|
94
|
+
update[i].forward[i] = x
|
95
|
+
end
|
101
96
|
|
102
|
-
|
103
|
-
|
104
|
-
end
|
97
|
+
# Increment the size attribute by one.
|
98
|
+
@size += 1
|
105
99
|
end
|
100
|
+
end
|
106
101
|
|
107
|
-
|
108
|
-
|
102
|
+
#-----------------------------------------------------------------------
|
103
|
+
# remove
|
104
|
+
#-----------------------------------------------------------------------
|
105
|
+
def remove(search_key, value)
|
106
|
+
update = []
|
109
107
|
|
110
|
-
|
108
|
+
x = @header
|
111
109
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
x = x.forward[0]
|
120
|
-
|
121
|
-
if x.key == search_key
|
122
|
-
x.value.delete(value)
|
110
|
+
@level.downto(0) do |i|
|
111
|
+
while x.forward[i].key < search_key
|
112
|
+
x = x.forward[i]
|
113
|
+
end
|
114
|
+
update[i] = x
|
115
|
+
end
|
123
116
|
|
124
|
-
|
125
|
-
0.upto(@level) do |i|
|
126
|
-
break unless update[i].forward[i] == x
|
127
|
-
update[i].forward[i] = x.forward[i]
|
128
|
-
end
|
117
|
+
x = x.forward[0]
|
129
118
|
|
130
|
-
|
131
|
-
|
132
|
-
end
|
119
|
+
if x.key == search_key
|
120
|
+
x.value.delete(value)
|
133
121
|
|
134
|
-
|
135
|
-
|
122
|
+
if x.value.empty?
|
123
|
+
0.upto(@level) do |i|
|
124
|
+
break unless update[i].forward[i] == x
|
125
|
+
update[i].forward[i] = x.forward[i]
|
136
126
|
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def search(search_key)
|
140
|
-
result = []
|
141
|
-
x = @header
|
142
127
|
|
143
|
-
@level
|
144
|
-
|
145
|
-
x = x.forward[i]
|
146
|
-
end
|
128
|
+
while @level > 0 and @header.forward[@level] == @footer
|
129
|
+
@level -= 1
|
147
130
|
end
|
148
131
|
|
149
|
-
|
150
|
-
|
151
|
-
return x.value if x.key == search_key
|
132
|
+
@size -= 1
|
133
|
+
end
|
152
134
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
135
|
+
end
|
136
|
+
|
137
|
+
#-----------------------------------------------------------------------
|
138
|
+
# search
|
139
|
+
#-----------------------------------------------------------------------
|
140
|
+
def search(search_key)
|
141
|
+
result = []
|
142
|
+
x = @header
|
143
|
+
|
144
|
+
@level.downto(0) do |i|
|
145
|
+
while x.forward[i].key < search_key
|
146
|
+
x = x.forward[i]
|
147
|
+
end
|
160
148
|
end
|
161
149
|
|
162
|
-
|
163
|
-
return search(other)
|
164
|
-
end
|
165
|
-
|
166
|
-
def >(search_key)
|
167
|
-
result = []
|
168
|
-
x = @header
|
169
|
-
|
170
|
-
@level.downto(0) do |i|
|
171
|
-
while x.forward[i].key < search_key
|
172
|
-
x = x.forward[i]
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
x = x.forward[0]
|
177
|
-
|
178
|
-
x = x.forward[0] if x.key == search_key
|
150
|
+
x = x.forward[0]
|
179
151
|
|
180
|
-
|
181
|
-
|
182
|
-
x = x.forward[0]
|
183
|
-
end
|
152
|
+
return x.value if x.key == search_key
|
153
|
+
end
|
184
154
|
|
185
|
-
|
155
|
+
#-----------------------------------------------------------------------
|
156
|
+
# one_of
|
157
|
+
#-----------------------------------------------------------------------
|
158
|
+
def one_of(*other)
|
159
|
+
result = []
|
160
|
+
other.each do |o|
|
161
|
+
result.concat(search(o))
|
162
|
+
end
|
163
|
+
return result
|
164
|
+
end
|
165
|
+
|
166
|
+
#-----------------------------------------------------------------------
|
167
|
+
# ==
|
168
|
+
#-----------------------------------------------------------------------
|
169
|
+
def ==(other)
|
170
|
+
search(other)
|
171
|
+
end
|
172
|
+
|
173
|
+
#-----------------------------------------------------------------------
|
174
|
+
# >
|
175
|
+
#-----------------------------------------------------------------------
|
176
|
+
def >(search_key)
|
177
|
+
result = []
|
178
|
+
x = @header
|
179
|
+
|
180
|
+
@level.downto(0) do |i|
|
181
|
+
while x.forward[i].key < search_key
|
182
|
+
x = x.forward[i]
|
183
|
+
end
|
186
184
|
end
|
187
185
|
|
188
|
-
|
189
|
-
|
190
|
-
x = @header
|
191
|
-
|
192
|
-
@level.downto(0) do |i|
|
193
|
-
while x.forward[i].key < search_key
|
194
|
-
x = x.forward[i]
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
x = x.forward[0]
|
186
|
+
x = x.forward[0]
|
187
|
+
x = x.forward[0] if x.key == search_key
|
199
188
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
return result
|
189
|
+
while x != @footer
|
190
|
+
result.concat(x.value)
|
191
|
+
x = x.forward[0]
|
206
192
|
end
|
207
193
|
|
208
|
-
|
209
|
-
|
210
|
-
x = @header
|
211
|
-
|
212
|
-
x = x.forward[0]
|
194
|
+
result
|
195
|
+
end
|
213
196
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
197
|
+
#-----------------------------------------------------------------------
|
198
|
+
# >=
|
199
|
+
#-----------------------------------------------------------------------
|
200
|
+
def >=(search_key)
|
201
|
+
result = []
|
202
|
+
x = @header
|
218
203
|
|
219
|
-
|
204
|
+
@level.downto(0) do |i|
|
205
|
+
while x.forward[i].key < search_key
|
206
|
+
x = x.forward[i]
|
207
|
+
end
|
220
208
|
end
|
221
|
-
|
222
|
-
def <=(search_key)
|
223
|
-
result = []
|
224
|
-
x = @header
|
225
209
|
|
226
|
-
|
227
|
-
|
228
|
-
while x != @footer and x.key <= search_key
|
229
|
-
result.concat(x.value)
|
230
|
-
x = x.forward[0]
|
231
|
-
end
|
210
|
+
x = x.forward[0]
|
232
211
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
def [](search_key)
|
237
|
-
return search(search_key)
|
212
|
+
while x != @footer
|
213
|
+
result.concat(x.value)
|
214
|
+
x = x.forward[0]
|
238
215
|
end
|
239
216
|
|
240
|
-
|
241
|
-
|
242
|
-
result = []
|
243
|
-
x = @header
|
217
|
+
result
|
218
|
+
end
|
244
219
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
x = x.forward[0]
|
220
|
+
#-----------------------------------------------------------------------
|
221
|
+
# <
|
222
|
+
#-----------------------------------------------------------------------
|
223
|
+
def <(search_key)
|
224
|
+
result = []
|
225
|
+
x = @header
|
252
226
|
|
253
|
-
|
227
|
+
x = x.forward[0]
|
254
228
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
end
|
259
|
-
|
260
|
-
result = result.concat(x.value) if x.key == search_end and end_inclusive
|
261
|
-
return result
|
229
|
+
while x != @footer and x.key < search_key
|
230
|
+
result.concat(x.value)
|
231
|
+
x = x.forward[0]
|
262
232
|
end
|
263
233
|
|
264
|
-
|
265
|
-
|
234
|
+
result
|
235
|
+
end
|
266
236
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
237
|
+
#-----------------------------------------------------------------------
|
238
|
+
# <=
|
239
|
+
#-----------------------------------------------------------------------
|
240
|
+
def <=(search_key)
|
241
|
+
result = []
|
242
|
+
x = @header
|
243
|
+
|
244
|
+
x = x.forward[0]
|
245
|
+
|
246
|
+
while x != @footer and x.key <= search_key
|
247
|
+
result.concat(x.value)
|
248
|
+
x = x.forward[0]
|
271
249
|
end
|
272
250
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
end
|
251
|
+
result
|
252
|
+
end
|
253
|
+
|
254
|
+
#-----------------------------------------------------------------------
|
255
|
+
# []
|
256
|
+
#-----------------------------------------------------------------------
|
257
|
+
def [](search_key)
|
258
|
+
search(search_key)
|
259
|
+
end
|
260
|
+
|
261
|
+
#-----------------------------------------------------------------------
|
262
|
+
# between
|
263
|
+
#-----------------------------------------------------------------------
|
264
|
+
def between(search_start, search_end, start_inclusive=false,
|
265
|
+
end_inclusive=false)
|
266
|
+
result = []
|
267
|
+
x = @header
|
268
|
+
|
269
|
+
@level.downto(0) do |i|
|
270
|
+
while x.forward[i].key < search_start
|
271
|
+
x = x.forward[i]
|
272
|
+
end
|
273
|
+
end
|
297
274
|
|
298
|
-
|
275
|
+
x = x.forward[0]
|
276
|
+
x = x.forward[0] if x.key == search_start and not start_inclusive
|
299
277
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
if lvl > @level
|
305
|
-
(@level + 1).upto(lvl) { |i| update[i] = @header }
|
306
|
-
@level = lvl
|
307
|
-
end
|
278
|
+
while x != @footer and x.key < search_end
|
279
|
+
result.concat(x.value)
|
280
|
+
x = x.forward[0]
|
281
|
+
end
|
308
282
|
|
309
|
-
|
310
|
-
|
283
|
+
result = result.concat(x.value) if x.key == search_end and end_inclusive
|
284
|
+
result
|
285
|
+
end
|
311
286
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
287
|
+
#-----------------------------------------------------------------------
|
288
|
+
# each
|
289
|
+
#-----------------------------------------------------------------------
|
290
|
+
def each
|
291
|
+
x = @header.forward[0]
|
292
|
+
|
293
|
+
while x != @footer
|
294
|
+
yield x.key, x.value
|
295
|
+
x = x.forward[0]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
#-----------------------------------------------------------------------
|
300
|
+
# load_from_hash
|
301
|
+
#-----------------------------------------------------------------------
|
302
|
+
def load_from_hash(sl_hash)
|
303
|
+
# Create array to hold last node that was at that level, while working
|
304
|
+
# backwards.
|
305
|
+
levels = []
|
306
|
+
0.upto(MAX_LEVEL) { |i| levels << @footer }
|
307
|
+
|
308
|
+
x = nil
|
309
|
+
lvl = nil
|
310
|
+
|
311
|
+
# Loop through keys in reverse order...
|
312
|
+
sl_hash.keys.sort.reverse.each do |key|
|
313
|
+
lvl, value = sl_hash[key]
|
314
|
+
|
315
|
+
# Update skiplist level to be same as highest level yet found in keys.
|
316
|
+
@level = lvl if lvl > @level
|
317
|
+
|
318
|
+
# Create node with values from hash.
|
319
|
+
x = Node.new(lvl, key, value)
|
320
|
+
|
321
|
+
# Now, for each level of node, point its forward reference to the last
|
322
|
+
# node that occupied that level (remember we are working backwards).
|
323
|
+
0.upto(lvl) do |i|
|
324
|
+
x.forward[i] = levels[i]
|
325
|
+
# Now, we want to make current node the last node that occupied that
|
326
|
+
# level.
|
327
|
+
levels[i] = x
|
328
|
+
end
|
329
|
+
|
330
|
+
# Now, make sure that, for now, the header node points to this node,
|
331
|
+
# since it is the lowest node, at least until we read the next hash key.
|
332
|
+
0.upto(lvl) { |i| @header.forward[i] = x }
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
#-----------------------------------------------------------------------
|
337
|
+
# dump_to_hash
|
338
|
+
#-----------------------------------------------------------------------
|
339
|
+
def dump_to_hash
|
340
|
+
sl_hash = {}
|
341
|
+
|
342
|
+
x = @header.forward[0]
|
343
|
+
|
344
|
+
while x != @footer
|
345
|
+
sl_hash[x.key] = [x.lvl, x.value]
|
346
|
+
x = x.forward[0]
|
323
347
|
end
|
324
348
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
349
|
+
sl_hash
|
350
|
+
end
|
351
|
+
|
352
|
+
#-----------------------------------------------------------------------
|
353
|
+
# dump
|
354
|
+
#-----------------------------------------------------------------------
|
355
|
+
def dump
|
356
|
+
File.open(@col.index_file_name + '.dump', 'w') do |fptr|
|
357
|
+
x = @header
|
358
|
+
fptr.write("---------------- Header -----------------------------\n")
|
359
|
+
x.forward.each_with_index do |f,i|
|
360
|
+
fptr.write("**** Forward entry %d ****\n" % i)
|
361
|
+
fptr.write("Key: " + f.key.inspect + "\n")
|
362
|
+
fptr.write("Value: " + f.value.inspect + "\n") unless \
|
363
|
+
f.is_a?(FooterNode)
|
364
|
+
end
|
365
|
+
fptr.write("---------------- End Header -------------------------\n")
|
366
|
+
|
367
|
+
while not x.forward[0].is_a?(FooterNode)
|
368
|
+
x = x.forward[0]
|
369
|
+
fptr.write("---------------- Node -------------------------\n")
|
370
|
+
fptr.write("Key: " + x.key.inspect + "\n")
|
371
|
+
fptr.write("Value: " + x.value.inspect + "\n")
|
372
|
+
fptr.write("Level: " + x.lvl.inspect + "\n")
|
329
373
|
x.forward.each_with_index do |f,i|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
puts '---------------- End Header -------------------------'
|
335
|
-
|
336
|
-
while not x.forward[0].is_a?(FooterNode)
|
337
|
-
x = x.forward[0]
|
338
|
-
puts '---------------- Node -------------------------'
|
339
|
-
puts x
|
340
|
-
puts x.key
|
341
|
-
puts x.value
|
342
|
-
puts x.lvl
|
343
|
-
x.forward.each_with_index do |f,i|
|
344
|
-
puts '**** Forward entry %d ****' % i
|
345
|
-
puts f.key
|
346
|
-
puts f.value unless f.is_a?(FooterNode)
|
347
|
-
end
|
348
|
-
puts '---------------- End Node ---------------------'
|
374
|
+
fptr.write("**** Forward entry %d ****\n" % i)
|
375
|
+
fptr.write("Key: " + f.key.inspect + "\n")
|
376
|
+
fptr.write("Value: " + f.value.inspect + "\n") unless \
|
377
|
+
f.is_a?(FooterNode)
|
349
378
|
end
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
379
|
+
fptr.write("---------------- End Node ---------------------\n")
|
380
|
+
end
|
381
|
+
|
382
|
+
fptr.write("--------------------- Footer ---------------------\n")
|
383
|
+
fptr.write(x.forward[0].inspect + "\n")
|
384
|
+
fptr.write("--------------------- End Footer -----------------\n")
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
#-----------------------------------------------------------------------
|
389
|
+
# PRIVATE METHODS
|
390
|
+
#-----------------------------------------------------------------------
|
391
|
+
private
|
392
|
+
|
393
|
+
#-----------------------------------------------------------------------
|
394
|
+
# random_level
|
395
|
+
#-----------------------------------------------------------------------
|
396
|
+
def random_level
|
397
|
+
lvl = 0
|
398
|
+
while rand < OPTIMAL_PROBABILITY and lvl < MAX_LEVEL
|
399
|
+
lvl += 1
|
354
400
|
end
|
401
|
+
lvl
|
402
|
+
end
|
403
|
+
end
|
355
404
|
|
356
|
-
private
|
357
405
|
|
358
|
-
|
359
|
-
|
406
|
+
#---------------------------------------------------------------------------
|
407
|
+
# HeaderNode Class
|
408
|
+
#---------------------------------------------------------------------------
|
409
|
+
class HeaderNode
|
410
|
+
attr_accessor :forward
|
411
|
+
attr_reader :key
|
360
412
|
|
361
|
-
|
362
|
-
|
363
|
-
|
413
|
+
def initialize
|
414
|
+
@forward = []
|
415
|
+
@key = HeaderKey.new
|
416
|
+
end
|
364
417
|
|
365
|
-
|
366
|
-
|
418
|
+
def inspect
|
419
|
+
self.class.to_s
|
420
|
+
end
|
367
421
|
end
|
368
422
|
|
369
|
-
class HeaderNode
|
370
|
-
attr_accessor :forward
|
371
|
-
attr_reader :key
|
372
|
-
|
373
|
-
def initialize
|
374
|
-
@forward = []
|
375
|
-
@key = HeaderKey.new
|
376
|
-
end
|
377
|
-
end
|
378
423
|
|
424
|
+
#---------------------------------------------------------------------------
|
425
|
+
# HeaderKey Class
|
426
|
+
#---------------------------------------------------------------------------
|
379
427
|
class HeaderKey
|
380
|
-
|
381
|
-
|
382
|
-
|
428
|
+
def ==(other)
|
429
|
+
false
|
430
|
+
end
|
383
431
|
end
|
384
432
|
|
433
|
+
|
434
|
+
#---------------------------------------------------------------------------
|
435
|
+
# FooterNode Class
|
436
|
+
#---------------------------------------------------------------------------
|
385
437
|
class FooterNode
|
386
|
-
|
438
|
+
attr_reader :key
|
387
439
|
|
388
|
-
|
389
|
-
|
390
|
-
|
440
|
+
def initialize
|
441
|
+
@key = FooterKey.new
|
442
|
+
end
|
443
|
+
|
444
|
+
def inspect
|
445
|
+
self.class.to_s
|
446
|
+
end
|
391
447
|
end
|
392
448
|
|
449
|
+
|
450
|
+
#---------------------------------------------------------------------------
|
451
|
+
# FooterKey Class
|
452
|
+
#---------------------------------------------------------------------------
|
393
453
|
class FooterKey
|
394
|
-
|
395
|
-
|
396
|
-
|
454
|
+
def inspect
|
455
|
+
self.class.to_s
|
456
|
+
end
|
457
|
+
|
458
|
+
def <(other)
|
459
|
+
false
|
460
|
+
end
|
397
461
|
end
|
398
462
|
|
399
463
|
|
464
|
+
#---------------------------------------------------------------------------
|
465
|
+
# Node Class
|
466
|
+
#---------------------------------------------------------------------------
|
400
467
|
class Node
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
468
|
+
attr_accessor :forward, :value
|
469
|
+
attr_reader :key, :lvl
|
470
|
+
|
471
|
+
def initialize(lvl, key, value)
|
472
|
+
@lvl = lvl
|
473
|
+
@forward = []
|
474
|
+
@key = key
|
475
|
+
@value = value
|
476
|
+
end
|
477
|
+
|
478
|
+
def inspect
|
479
|
+
self.class.to_s
|
480
|
+
end
|
410
481
|
end
|
411
482
|
|
412
483
|
end
|