mongoose 0.1.0 → 0.1.1

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