mongoose 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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