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 CHANGED
@@ -1,4 +1,4 @@
1
- = Mongoose 0.1.0
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
+
@@ -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.
@@ -5,9 +5,11 @@ rescue LoadError
5
5
  require 'mongoose'
6
6
  end
7
7
 
8
-
9
- Dir.glob('[plane|pilot|flight]*.mg?').each do |filename|
10
- File.delete(filename)
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
- db.create_table(:plane) do |tbl|
22
- tbl.add_indexed_column(:plane_name, :string)
23
- tbl.add_column(:country, :string)
24
- tbl.add_indexed_column(:speed, :integer)
25
- tbl.add_column(:range, :integer)
26
- end
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
@@ -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'
@@ -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') do |fptr|
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') do |fptr|
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 do |rec, fpos|
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)
@@ -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 = get_class_name_from_table_name(table_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 = get_class_name_from_table_name(table_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 = get_class_name_from_table_name(table_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
@@ -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
- VERSION = "0.1"
10
- MAX_LEVEL = 31
11
- OPTIMAL_PROBABILITY = 0.25
12
-
13
- attr_reader :size
14
-
15
- #-----------------------------------------------------------------------
16
- # initialize
17
- #-----------------------------------------------------------------------
18
- #++
19
- # Create a new skip list instance.
20
- #
21
- def initialize
22
- @size = 0
23
-
24
- # Create header and footer nodes.
25
- @footer = FooterNode.new
26
- @header = HeaderNode.new
27
-
28
- # Point all header.forward references to footer node.
29
- 0.upto(MAX_LEVEL) { |i| @header.forward[i] = @footer }
30
-
31
- # This attribute will hold the actual level of the skip list.
32
- @level = 0
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
- # store
37
- #-----------------------------------------------------------------------
38
- #++
39
- # If key not found, will insert new record, otherwise will update value
40
- # of existing record.
41
- #
42
- def store(search_key, new_value)
43
- # This array will be used to determine which records need their
44
- # forward array re-adjusted after a new node is created.
45
- update = []
46
-
47
- # Start off at the header node.
48
- x = @header
49
-
50
- # Starting at the current highest level of the skip list, walk from
51
- # left to right at that level, until you see that the next node's
52
- # key is greater than the key you are searching for. When this
53
- # happend, you want to add the current node you are on to the list
54
- # of nodes that need to have their forward arrays updated. Next,
55
- # you want to drop down a level on the current node and start
56
- # walking forward again, until you again see that the next node's
57
- # key is bigger than the search key. In this way, you are walking
58
- # through the skip list, constantly moving to the right and moving
59
- # down, until you reach level 0 and are on the node whose key is
60
- # either equal to the search key (in which case an update will take
61
- # place), or the highest key in the list that is still lower than
62
- # the search key (in which case, you have found the place to do an
63
- # insert).
64
- @level.downto(0) do |i|
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
- # Increment the size attribute by one.
103
- @size += 1
104
- end
97
+ # Increment the size attribute by one.
98
+ @size += 1
105
99
  end
100
+ end
106
101
 
107
- def remove(search_key, value)
108
- update = []
102
+ #-----------------------------------------------------------------------
103
+ # remove
104
+ #-----------------------------------------------------------------------
105
+ def remove(search_key, value)
106
+ update = []
109
107
 
110
- x = @header
108
+ x = @header
111
109
 
112
- @level.downto(0) do |i|
113
- while x.forward[i].key < search_key
114
- x = x.forward[i]
115
- end
116
- update[i] = x
117
- end
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
- if x.value.empty?
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
- while @level > 0 and @header.forward[@level] == @footer
131
- @level -= 1
132
- end
119
+ if x.key == search_key
120
+ x.value.delete(value)
133
121
 
134
- @size -= 1
135
- end
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.downto(0) do |i|
144
- while x.forward[i].key < search_key
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
- x = x.forward[0]
150
-
151
- return x.value if x.key == search_key
132
+ @size -= 1
133
+ end
152
134
  end
153
-
154
- def one_of(*other)
155
- result = []
156
- other.each do |o|
157
- result.concat(search(o))
158
- end
159
- return result
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
- def ==(other)
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
- while x != @footer
181
- result.concat(x.value)
182
- x = x.forward[0]
183
- end
152
+ return x.value if x.key == search_key
153
+ end
184
154
 
185
- return result
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
- def >=(search_key)
189
- result = []
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
- while x != @footer
201
- result.concat(x.value)
202
- x = x.forward[0]
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
- def <(search_key)
209
- result = []
210
- x = @header
211
-
212
- x = x.forward[0]
194
+ result
195
+ end
213
196
 
214
- while x != @footer and x.key < search_key
215
- result.concat(x.value)
216
- x = x.forward[0]
217
- end
197
+ #-----------------------------------------------------------------------
198
+ # >=
199
+ #-----------------------------------------------------------------------
200
+ def >=(search_key)
201
+ result = []
202
+ x = @header
218
203
 
219
- return result
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
- x = x.forward[0]
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
- return result
234
- end
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
- def between(search_start, search_end, start_inclusive=false,
241
- end_inclusive=false)
242
- result = []
243
- x = @header
217
+ result
218
+ end
244
219
 
245
- @level.downto(0) do |i|
246
- while x.forward[i].key < search_start
247
- x = x.forward[i]
248
- end
249
- end
250
-
251
- x = x.forward[0]
220
+ #-----------------------------------------------------------------------
221
+ # <
222
+ #-----------------------------------------------------------------------
223
+ def <(search_key)
224
+ result = []
225
+ x = @header
252
226
 
253
- x = x.forward[0] if x.key == search_start and not start_inclusive
227
+ x = x.forward[0]
254
228
 
255
- while x != @footer and x.key < search_end
256
- result.concat(x.value)
257
- x = x.forward[0]
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
- def each
265
- x = @header.forward[0]
234
+ result
235
+ end
266
236
 
267
- while x != @footer
268
- yield x.key, x.value
269
- x = x.forward[0]
270
- end
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
- def load(key, value)
274
- # This array will be used to determine which records need their
275
- # forward array re-adjusted after a new node is created.
276
- update = []
277
-
278
- # Start off at the header node.
279
- x = @header
280
-
281
- # Starting at the current highest level of the skip list, walk from
282
- # left to right at that level, until you reach the footer node. When
283
- # this happens, you want to add the current node you are on to the list
284
- # of nodes that need to have their forward arrays updated. Next,
285
- # you want to drop down a level on the current node and start
286
- # walking forward again, until you again see that the next node is the
287
- # footer node. In this way, you are walking through the skip list,
288
- # constantly moving to the right and moving down, until you reach level
289
- # 0 and are on the node whose key is the highest key in the list (in
290
- # which case, you have found the place to do an insert).
291
- @level.downto(0) do |i|
292
- while x.forward[i] != @footer
293
- x = x.forward[i]
294
- end
295
- update[i] = x
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
- lvl = random_level
275
+ x = x.forward[0]
276
+ x = x.forward[0] if x.key == search_start and not start_inclusive
299
277
 
300
- # If the new level is higher than the actual current level, we
301
- # need to make sure that the header node gets updated at these
302
- # levels. Then, we set the actual current level equal to the
303
- # new level.
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
- # Create a new node.
310
- x = Node.new(lvl, key, value)
283
+ result = result.concat(x.value) if x.key == search_end and end_inclusive
284
+ result
285
+ end
311
286
 
312
- # Now, we need to update all of the nodes that will be affected
313
- # by the insertion of the new node. These are nodes whose
314
- # forward array either will point to the new node and the new
315
- # node itself.
316
- 0.upto(lvl) do |i|
317
- x.forward[i] = update[i].forward[i]
318
- update[i].forward[i] = x
319
- end
320
-
321
- # Increment the size attribute by one.
322
- @size += 1
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
- def dump
326
- x = @header
327
- puts '---------------- Header -----------------------------'
328
- puts x
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
- puts '**** Forward entry %d ****' % i
331
- puts f.key
332
- puts f.value unless f.is_a?(FooterNode)
333
- end
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
- puts '--------------------- Footer ---------------------'
352
- pp x.forward[0]
353
- puts '--------------------- End Footer -----------------'
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
- def random_level
359
- lvl = 0
406
+ #---------------------------------------------------------------------------
407
+ # HeaderNode Class
408
+ #---------------------------------------------------------------------------
409
+ class HeaderNode
410
+ attr_accessor :forward
411
+ attr_reader :key
360
412
 
361
- while rand < OPTIMAL_PROBABILITY and lvl < MAX_LEVEL
362
- lvl += 1
363
- end
413
+ def initialize
414
+ @forward = []
415
+ @key = HeaderKey.new
416
+ end
364
417
 
365
- return lvl
366
- end
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
- def ==(other)
381
- false
382
- end
428
+ def ==(other)
429
+ false
430
+ end
383
431
  end
384
432
 
433
+
434
+ #---------------------------------------------------------------------------
435
+ # FooterNode Class
436
+ #---------------------------------------------------------------------------
385
437
  class FooterNode
386
- attr_reader :key
438
+ attr_reader :key
387
439
 
388
- def initialize
389
- @key = FooterKey.new
390
- end
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
- def <(other)
395
- return false
396
- end
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
- attr_accessor :forward, :value
402
- attr_reader :forward, :key, :lvl
403
-
404
- def initialize(lvl, key, value)
405
- @lvl = lvl
406
- @forward = []
407
- @key = key
408
- @value = value
409
- end
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