mongoose 0.1.0

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 ADDED
@@ -0,0 +1,136 @@
1
+ = Mongoose 0.1.0
2
+
3
+ A database management system written in Ruby. It has an ActiveRecord-like
4
+ interface, uses Skiplists for its indexing, and Marshal for its data
5
+ serialization. I named it Mongoose, because, like Rudyard Kipling's
6
+ Rikki-Tikki-Tavi, my aim is for it to be small, quick, and friendly.
7
+
8
+
9
+ == Credits
10
+
11
+ Thanks to Logan Capaldo for letting me steal a lot of the code from KirbyRecord.
12
+
13
+ Thanks to Ezra Zygmuntowicz and Fabien Franzen, whose ez_where Rails plugin,
14
+ provided much of the inspiration for the query language.
15
+
16
+ Thanks to everyone who gave me feedback on KirbyBase. I have tried to put all
17
+ the lessons learned from developing that library to good use here.
18
+
19
+
20
+ == Installation
21
+
22
+ Unpack the file you downloaded. Execute "ruby setup.rb".
23
+
24
+
25
+ == Features
26
+
27
+ * Pure Ruby, with no external dependencies.
28
+ * ActiveRecord-like interface.
29
+ * Fast queries on indexed fields (Up to 10x faster than KirbyBase). Indexes
30
+ are Skiplists, which are just plain fun to play around with.
31
+ * Not an in-memory database. Data is only read in from disk when needed and
32
+ changes are immediately written out to disk.
33
+ * In-memory indexes are initialized from dedicated index files, rather than
34
+ rebuilt from scratch upon database initialization (like KirbyBase does). This
35
+ can greatly reduce startup times.
36
+ * Supports any datatype that Marshal supports.
37
+ * Table relations supported via has_one, has_many.
38
+
39
+
40
+ == Documentation
41
+
42
+ Right now, documentation is a little light. It will get better. See the
43
+ examples directory for examples of how to use Mongoose. Here's a quick
44
+ tutorial:
45
+
46
+ require 'mongoose'
47
+
48
+ # Create a class for your table.
49
+ class Plane < Mongoose::Table
50
+ validates_presence_of :name, :speed
51
+ end
52
+
53
+ # Create a database instance.
54
+ db = Mongoose::Database.new
55
+
56
+ # Create new table. Notice how you specify whether a column is indexed or not.
57
+ db.create_table(:plane) do |tbl|
58
+ tbl.add_indexed_column(:plane_name, :string)
59
+ tbl.add_column(:country, :string)
60
+ tbl.add_indexed_column(:speed, :integer)
61
+ tbl.add_column(:range, :integer)
62
+ end
63
+
64
+ # Add a record.
65
+ rec = Plane.new
66
+ rec.plane_name = 'P-51'
67
+ rec.country = 'USA'
68
+ rec.speed = 402
69
+ rec.range = 1205
70
+ rec.save
71
+
72
+ # Various ways to find a record; should be familiar to ActiveRecord users.
73
+ Plane.find(1) # Find record with id equal 1.
74
+
75
+ Plane.find { |plane| plane.speed > 350 } # Find all planes with speed > 350.
76
+
77
+ Plane.find # Find all records.
78
+
79
+ Plane.find(:first) { |plane| plane.country == 'USA' }
80
+
81
+ Plane.find do |plane| # Find all planes from either USA or
82
+ plane.any do # Great Britain with speed > 400.
83
+ plane.country == 'USA'
84
+ plane.country == 'Great Britain'
85
+ end
86
+ plane.speed > 400
87
+ end
88
+
89
+ # Delete a record.
90
+ Plane.find(1).destroy
91
+
92
+ == Manifest
93
+
94
+ * README - this file
95
+ * install.rb - install script
96
+ * changes.txt - history of changes.
97
+ * lib directory - dbms module
98
+ * test directory - unit tests
99
+ * examples directory - many example scripts demonstrating features.
100
+ * images directory - images used in manual.
101
+
102
+
103
+ == Author
104
+
105
+ Written in 2006 by Jamey Cribbs <mailto:jcribbs@netpromi.com>
106
+
107
+
108
+ == License
109
+
110
+ Mongoose is distributed under the same license as Ruby.
111
+
112
+ Copyright (c) 2006 Jamey Cribbs
113
+
114
+
115
+ == Warranty
116
+
117
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
118
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
119
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
120
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
121
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
122
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
123
+ IN THE SOFTWARE.
124
+
125
+
126
+ == Feedback
127
+
128
+ Please send any bug reports, suggestions, ideas,
129
+ improvements, to:
130
+
131
+ jcribbs@netpromi.com
132
+
133
+
134
+ == Home Page
135
+
136
+ http://rubyforge.org/projects/mongoose/
@@ -0,0 +1,2 @@
1
+ 2006-07-19:: Version 0.1.0
2
+ * Initial release.
@@ -0,0 +1,117 @@
1
+ begin
2
+ require 'rubygems'
3
+ require_gem 'Mongoose'
4
+ rescue LoadError
5
+ require 'mongoose'
6
+ end
7
+
8
+
9
+ Dir.glob('[plane|pilot|flight]*.mg?').each do |filename|
10
+ File.delete(filename)
11
+ end
12
+
13
+ class Pilot < Mongoose::Table
14
+ has_one :plane
15
+ has_many :flights
16
+ end
17
+
18
+ class Plane < Mongoose::Table
19
+ belongs_to :pilot
20
+ end
21
+
22
+ class Flight < Mongoose::Table
23
+ belongs_to :pilot
24
+ end
25
+
26
+ # Create a database instance.
27
+ db = Mongoose::Database.new
28
+
29
+ # Create tables.
30
+ db.create_table(:pilot) do |tbl|
31
+ tbl.add_indexed_column(:pilot_name, :string)
32
+ tbl.add_column(:years_flying, :integer)
33
+ end
34
+ db.create_table(:plane) do |tbl|
35
+ tbl.add_indexed_column(:plane_name, :string)
36
+ tbl.add_column(:country, :string)
37
+ tbl.add_indexed_column(:speed, :integer)
38
+ tbl.add_column(:range, :integer)
39
+ tbl.add_column(:pilot_id, :integer)
40
+ end
41
+ db.create_table(:flight) do |tbl|
42
+ tbl.add_column(:origin, :string)
43
+ tbl.add_column(:destination, :string)
44
+ tbl.add_column(:pilot_id, :integer)
45
+ end
46
+
47
+ # Create Pilot records.
48
+ doolittle = Pilot.new
49
+ doolittle.pilot_name = 'Doolitle, James'
50
+ doolittle.years_flying = 15
51
+ doolittle.save
52
+
53
+ amelia = Pilot.new
54
+ amelia.pilot_name = 'Earhart, Amelia'
55
+ amelia.years_flying = 10
56
+ amelia.save
57
+
58
+ # Create Plane records.
59
+ rec = Plane.new
60
+ rec.plane_name = 'P-51'
61
+ rec.country = 'USA'
62
+ rec.speed = 402
63
+ rec.range = 1205
64
+ rec.pilot_id = doolittle.id
65
+ rec.save
66
+
67
+ rec = Plane.new
68
+ rec.plane_name = 'Spitfire'
69
+ rec.country = 'Great Britain'
70
+ rec.speed = 333
71
+ rec.range = 454
72
+ rec.pilot_id = amelia.id
73
+ rec.save
74
+
75
+ # Create Flight records.
76
+ rec = Flight.new
77
+ rec.pilot_id = doolittle.id
78
+ rec.origin = 'Army'
79
+ rec.destination = 'Nowhere'
80
+ rec.save
81
+
82
+ rec = Flight.new
83
+ rec.pilot_id = amelia.id
84
+ rec.origin = 'USA'
85
+ rec.destination = 'France'
86
+
87
+ rec.save
88
+ rec = Flight.new
89
+ rec.pilot_id = amelia.id
90
+ rec.origin = 'USA'
91
+ rec.destination = 'Phillipines'
92
+ rec.save
93
+
94
+ rec = Flight.new
95
+ rec.pilot_id = amelia.id
96
+ rec.origin = 'China'
97
+ rec.destination = 'Unknown'
98
+ rec.save
99
+
100
+ puts "\n\nFind Jimmy Doolittle"
101
+ rec = Pilot.find(1)
102
+ p rec
103
+ puts "\nShow his plane"
104
+ p rec.plane
105
+
106
+ puts "\n\nShow flights that belong to Amelia"
107
+ p amelia.flights
108
+
109
+ puts "\n\nAdd another flight to Amelia's collection and redisplay"
110
+ rec = Flight.new
111
+ rec.pilot_id = amelia.id
112
+ rec.origin = 'Tuscon'
113
+ rec.destination = 'Phoenix'
114
+ amelia.flights << rec
115
+ p amelia.flights
116
+
117
+ db.close
@@ -0,0 +1,117 @@
1
+ begin
2
+ require 'rubygems'
3
+ require_gem 'Mongoose'
4
+ rescue LoadError
5
+ require 'mongoose'
6
+ end
7
+
8
+
9
+ Dir.glob('[plane|pilot|flight]*.mg?').each do |filename|
10
+ File.delete(filename)
11
+ end
12
+
13
+ class Plane < Mongoose::Table
14
+ validates_presence_of :name, :speed
15
+ end
16
+
17
+ # Create a database instance.
18
+ db = Mongoose::Database.new
19
+
20
+ # 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
27
+
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
57
+ rec4.save
58
+ rescue RuntimeError => e
59
+ puts e
60
+ end
61
+
62
+ rec4.speed = 339
63
+ rec4.save
64
+
65
+ puts "\n\nFind P-51 record by ID"
66
+ p_51 = Plane.find(1)
67
+ p p_51
68
+
69
+ if p_51
70
+ # Change speed on P-51 record and save.
71
+ p_51.speed = 405
72
+ p_51.save
73
+ end
74
+
75
+ puts "\n\nFind all records with speed greater than 350 mph"
76
+ result = Plane.find { |plane| plane.speed > 350 }
77
+ p result
78
+
79
+ puts "\n\nFind all US planes with speed greater than 300 mph"
80
+ result = Plane.find { |plane| plane.country == 'USA' and plane.speed > 300 }
81
+ p result
82
+
83
+ puts "\n\nFind all British planes with speed greater than 300 mph"
84
+ result = Plane.find do |plane|
85
+ plane.country == 'Great Britain' and plane.speed > 300
86
+ end
87
+ p result
88
+
89
+ puts "\n\nFind all Allied planes"
90
+ result = Plane.find do |plane|
91
+ plane.any do
92
+ plane.country == 'USA'
93
+ plane.country == 'Great Britain'
94
+ end
95
+ end
96
+ p result
97
+
98
+ puts "\n\nFind all Allied planes with speed greater than 400 mph"
99
+ result = Plane.find do |plane|
100
+ plane.any do
101
+ plane.country == 'USA'
102
+ plane.country == 'Great Britain'
103
+ end
104
+ plane.speed > 400
105
+ end
106
+ p result
107
+
108
+ # Delete Spitfire record.
109
+ spitfire = Plane.find(:first) { |plane| plane.plane_name == 'Spitfire' }
110
+
111
+ spitfire.destroy if spitfire
112
+
113
+ puts "\n\nFind all records in table."
114
+ result = Plane.find
115
+ p result
116
+
117
+ db.close
@@ -0,0 +1,45 @@
1
+ require 'yaml'
2
+ require 'mongoose/database'
3
+ require 'mongoose/table'
4
+ require 'mongoose/column'
5
+ require 'mongoose/skiplist'
6
+ require 'mongoose/linear_search'
7
+ require 'mongoose/util'
8
+
9
+ #
10
+ # :main:Mongoose
11
+ # :title:Mongoose Module Documentation
12
+ # Mongoose is a library that implements a database management system. It is
13
+ # written in Ruby so it runs anywhere Ruby runs. It uses Skiplists for its
14
+ # indexes, so queries are fast. It uses Marshal to store its data so data
15
+ # retrieval is fast.
16
+ #
17
+ # Author:: Jamey Cribbs (mailto:jcribbs@netpromi.com)
18
+ # Homepage:: http://rubyforge.org/projects/mongoose/
19
+ # Copyright:: Copyright (c) 2006 NetPro Technologies, LLC
20
+ # License:: Distributed under the same terms as Ruby
21
+ # History:
22
+ # 2006-07-19:: Version 0.1.0
23
+ # * Initial release.
24
+ #
25
+ #
26
+ module Mongoose
27
+
28
+ DATA_TYPES = [:string, :integer, :float, :time, :date, :datetime, :boolean]
29
+ TBL_EXT = '.mgt'
30
+ TBL_HDR_EXT = '.mgh'
31
+ TBL_IDX_EXT = '.mgi'
32
+
33
+ end
34
+
35
+ #---------------------------------------------------------------------------
36
+ # Object
37
+ #---------------------------------------------------------------------------
38
+ class Object
39
+ def full_const_get(name)
40
+ list = name.split("::")
41
+ obj = Object
42
+ list.each {|x| obj = obj.const_get(x) }
43
+ obj
44
+ end
45
+ end
@@ -0,0 +1,286 @@
1
+ require 'forwardable'
2
+
3
+ module Mongoose
4
+
5
+ #-------------------------------------------------------------------------------
6
+ # BaseColumn class
7
+ #-------------------------------------------------------------------------------
8
+ class BaseColumn
9
+ attr_reader :name, :data_type, :tbl_class
10
+ attr_writer :required
11
+
12
+ private_class_method :new
13
+
14
+ def self.valid_data_type?(data_type)
15
+ DATA_TYPES.include?(data_type)
16
+ end
17
+
18
+ def self.create(tbl_class, name, col_def)
19
+ return new(tbl_class, name, col_def)
20
+ end
21
+
22
+ def initialize(tbl_class, name, col_def)
23
+ @tbl_class = tbl_class
24
+ @name = name
25
+ @data_type = col_def
26
+ @indexed = false
27
+ @required = false
28
+ end
29
+
30
+ def indexed?
31
+ @indexed
32
+ end
33
+
34
+ def required?
35
+ @required
36
+ end
37
+
38
+ def close
39
+ end
40
+
41
+ end
42
+
43
+
44
+ #-------------------------------------------------------------------------------
45
+ # Column class
46
+ #-------------------------------------------------------------------------------
47
+ class Column < BaseColumn
48
+ def initialize(tbl_class, name, col_def)
49
+ super
50
+ @idx = LinearSearch.new(self)
51
+ end
52
+
53
+ def store(key, value)
54
+ end
55
+
56
+ def remove(key, value)
57
+ end
58
+
59
+ def >(other)
60
+ @tbl_class.query << [@idx, :>, other]
61
+ end
62
+
63
+ def >=(other)
64
+ @tbl_class.query << [@idx, :>=, other]
65
+ end
66
+
67
+ def <(other)
68
+ @tbl_class.query << [@idx, :<, other]
69
+ end
70
+
71
+ def <=(other)
72
+ @tbl_class.query << [@idx, :<=, other]
73
+ end
74
+
75
+ def ==(other)
76
+ @tbl_class.query << [@idx, :==, other]
77
+ end
78
+
79
+ def between(*other)
80
+ @tbl_class.query << [@idx, :between, other]
81
+ end
82
+
83
+ def one_of(*other)
84
+ @tbl_class.query << [@idx, :one_of, other]
85
+ end
86
+ end
87
+
88
+
89
+ #-------------------------------------------------------------------------------
90
+ # IndexedColumn class
91
+ #-------------------------------------------------------------------------------
92
+ class IndexedColumn < BaseColumn
93
+ attr_reader :idx, :index_file_name
94
+
95
+ #-----------------------------------------------------------------------------
96
+ # initialize
97
+ #-----------------------------------------------------------------------------
98
+ def initialize(tbl_class, name, col_def)
99
+ super
100
+ @indexed = true
101
+
102
+ @index_file_name = File.join(@tbl_class.db.path,
103
+ @tbl_class.table_name.to_s + '_' + @name.to_s + TBL_IDX_EXT)
104
+ end
105
+
106
+ #-----------------------------------------------------------------------------
107
+ # close
108
+ #-----------------------------------------------------------------------------
109
+ def close
110
+ rebuild_index_file if index_file_out_of_date?
111
+ end
112
+
113
+ def init_index
114
+ if File.exists?(@index_file_name) and not index_file_out_of_date?
115
+ rebuild_index_from_index_file
116
+ else
117
+ rebuild_index_from_table
118
+ end
119
+ end
120
+
121
+ #-----------------------------------------------------------------------------
122
+ # with_index_file
123
+ #-----------------------------------------------------------------------------
124
+ def with_index_file(access='r')
125
+ begin
126
+ yield fptr = open(@index_file_name, access)
127
+ ensure
128
+ fptr.close
129
+ end
130
+ end
131
+
132
+ #-----------------------------------------------------------------------------
133
+ # index_file_out_of_date?
134
+ #-----------------------------------------------------------------------------
135
+ def index_file_out_of_date?
136
+ if not File.exists?(@index_file_name) or (File.mtime(File.join(
137
+ @tbl_class.db.path, @tbl_class.table_name.to_s + TBL_EXT)) >
138
+ File.mtime(@index_file_name))
139
+ true
140
+ else
141
+ false
142
+ end
143
+ end
144
+ end
145
+
146
+
147
+ #-------------------------------------------------------------------------------
148
+ # SkipListIndexColumn class
149
+ #-------------------------------------------------------------------------------
150
+ class SkipListIndexColumn < IndexedColumn
151
+ def initialize(tbl_class, name, col_def)
152
+ @idx = SkipList.new
153
+ super
154
+ end
155
+
156
+ def clear_index
157
+ @idx = SkipList.new
158
+ end
159
+
160
+ def >(other)
161
+ @tbl_class.query << [@idx, :>, other]
162
+ end
163
+
164
+ def >=(other)
165
+ @tbl_class.query << [@idx, :>=, other]
166
+ end
167
+
168
+ def <(other)
169
+ @tbl_class.query << [@idx, :<, other]
170
+ end
171
+
172
+ def <=(other)
173
+ @tbl_class.query << [@idx, :<=, other]
174
+ end
175
+
176
+ def ==(other)
177
+ @tbl_class.query << [@idx, :==, other]
178
+ end
179
+
180
+ def search(other)
181
+ @tbl_class.query << [@idx, :search, other]
182
+ end
183
+
184
+ def between(*other)
185
+ @tbl_class.query << [@idx, :between, other]
186
+ end
187
+
188
+ def one_of(*other)
189
+ @tbl_class.query << [@idx, :one_of, other]
190
+ end
191
+
192
+ def rebuild_index_file
193
+ with_index_file('w') do |fptr|
194
+ @idx.each { |k,v| fptr.write(Marshal.dump([k, v])) }
195
+ end
196
+ end
197
+
198
+ def rebuild_index_from_table
199
+ clear_index
200
+ i = @tbl_class.columns.index(self)
201
+
202
+ @tbl_class.get_all_recs do |rec, fpos|
203
+ add_index_rec(rec[i], rec[0]) unless rec[i].nil?
204
+ end
205
+ end
206
+
207
+ def rebuild_index_from_index_file
208
+ 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
218
+ end
219
+
220
+ def add_index_rec(key, value)
221
+ @idx.store(key, value)
222
+ end
223
+
224
+ def remove_index_rec(key, value)
225
+ @idx.remove(key, value)
226
+ end
227
+ end
228
+
229
+
230
+ #-------------------------------------------------------------------------------
231
+ # IDColumn class
232
+ #-------------------------------------------------------------------------------
233
+ class IDColumn < IndexedColumn
234
+ extend Forwardable
235
+
236
+ def_delegator(:@idx, :[], :[])
237
+ def_delegator(:@idx, :keys, :keys)
238
+ # def_delegator(:@idx, :delete, :remove)
239
+
240
+ def initialize(tbl_class, name, col_def)
241
+ @idx = {}
242
+ super
243
+ end
244
+
245
+ def clear_index
246
+ @idx = {}
247
+ end
248
+
249
+ 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
254
+ end
255
+
256
+ def rebuild_index_from_table
257
+ clear_index
258
+ @tbl_class.get_all_recs do |rec, fpos|
259
+ add_index_rec(rec[0], fpos)
260
+ end
261
+ end
262
+
263
+ def rebuild_index_from_index_file
264
+ 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
275
+ end
276
+
277
+ def add_index_rec(id, fpos)
278
+ @idx[id] = fpos
279
+ end
280
+
281
+ def remove_index_rec(id)
282
+ @idx.delete(id)
283
+ end
284
+ end
285
+
286
+ end