sequel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,357 @@
1
+ module Sequel
2
+ # A Dataset represents a view of a the data in a database, constrained by
3
+ # specific parameters such as filtering conditions, order, etc. Datasets
4
+ # can be used to create, retrieve, update and delete records.
5
+ #
6
+ # Query results are always retrieved on demand, so a dataset can be kept
7
+ # around and reused indefinitely:
8
+ # my_posts = DB[:posts].filter(:author => 'david') # no records are retrieved
9
+ # p my_posts.all # records are now retrieved
10
+ # ...
11
+ # p my_posts.all # records are retrieved again
12
+ #
13
+ # In order to provide this functionality, dataset methods such as where,
14
+ # select, order, etc. return modified copies of the dataset, so you can
15
+ # use different datasets to access data:
16
+ # posts = DB[:posts]
17
+ # davids_posts = posts.filter(:author => 'david')
18
+ # old_posts = posts.filter('stamp < ?', 1.week.ago)
19
+ #
20
+ # Datasets are Enumerable objects, so they can be manipulated using any
21
+ # of the Enumerable methods, such as map, inject, etc.
22
+ class Dataset
23
+ include Enumerable
24
+
25
+ attr_reader :db
26
+ attr_accessor :record_class
27
+
28
+ # Constructs a new instance of a dataset with a database instance, initial
29
+ # options and an optional record class. Datasets are usually constructed by
30
+ # invoking Database methods:
31
+ # DB[:posts]
32
+ # Or:
33
+ # DB.dataset # the returned dataset is blank
34
+ #
35
+ # Sequel::Dataset is an abstract class that is not useful by itself. Each
36
+ # database adaptor should provide a descendant class of Sequel::Dataset.
37
+ def initialize(db, opts = {}, record_class = nil)
38
+ @db = db
39
+ @opts = opts || {}
40
+ @record_class = record_class
41
+ end
42
+
43
+ # Returns a new instance of the dataset with its options
44
+ def dup_merge(opts)
45
+ self.class.new(@db, @opts.merge(opts), @record_class)
46
+ end
47
+
48
+ AS_REGEXP = /(.*)___(.*)/.freeze
49
+ AS_FORMAT = "%s AS %s".freeze
50
+ DOUBLE_UNDERSCORE = '__'.freeze
51
+ PERIOD = '.'.freeze
52
+
53
+ # Returns a valid SQL fieldname as a string. Field names specified as
54
+ # symbols can include double underscores to denote a dot separator, e.g.
55
+ # :posts__id will be converted into posts.id.
56
+ def field_name(field)
57
+ field.is_a?(Symbol) ? field.to_field_name : field
58
+ end
59
+
60
+ QUALIFIED_REGEXP = /(.*)\.(.*)/.freeze
61
+ QUALIFIED_FORMAT = "%s.%s".freeze
62
+
63
+ # Returns a qualified field name (including a table name) if the field
64
+ # name isn't already qualified.
65
+ def qualified_field_name(field, table)
66
+ fn = field_name(field)
67
+ fn = QUALIFIED_FORMAT % [table, fn] unless fn =~ QUALIFIED_REGEXP
68
+ end
69
+
70
+ WILDCARD = '*'.freeze
71
+ COMMA_SEPARATOR = ", ".freeze
72
+
73
+ # Converts a field list into a comma seperated string of field names.
74
+ def field_list(fields)
75
+ case fields
76
+ when Array:
77
+ if fields.empty?
78
+ WILDCARD
79
+ else
80
+ fields.map {|i| field_name(i)}.join(COMMA_SEPARATOR)
81
+ end
82
+ when Symbol:
83
+ fields.to_field_name
84
+ else
85
+ fields
86
+ end
87
+ end
88
+
89
+ # Converts an array of sources into a comma separated list.
90
+ def source_list(source)
91
+ case source
92
+ when Array: source.join(COMMA_SEPARATOR)
93
+ else source
94
+ end
95
+ end
96
+
97
+ # Returns a literal representation of a value to be used as part
98
+ # of an SQL expression. This method is overriden in descendants.
99
+ def literal(v)
100
+ case v
101
+ when String: "'%s'" % v
102
+ else v.to_s
103
+ end
104
+ end
105
+
106
+ AND_SEPARATOR = " AND ".freeze
107
+ EQUAL_COND = "(%s = %s)".freeze
108
+
109
+ # Formats an equality condition SQL expression.
110
+ def where_equal_condition(left, right)
111
+ EQUAL_COND % [field_name(left), literal(right)]
112
+ end
113
+
114
+ # Formats a where clause.
115
+ def where_list(where)
116
+ case where
117
+ when Hash:
118
+ where.map {|kv| where_equal_condition(kv[0], kv[1])}.join(AND_SEPARATOR)
119
+ when Array:
120
+ fmt = where.shift
121
+ fmt.gsub('?') {|i| literal(where.shift)}
122
+ else
123
+ where
124
+ end
125
+ end
126
+
127
+ # Formats a join condition.
128
+ def join_cond_list(cond, join_table)
129
+ cond.map do |kv|
130
+ EQUAL_COND % [
131
+ qualified_field_name(kv[0], join_table),
132
+ qualified_field_name(kv[1], @opts[:from])]
133
+ end.join(AND_SEPARATOR)
134
+ end
135
+
136
+ # Returns a copy of the dataset with the source changed.
137
+ def from(source)
138
+ dup_merge(:from => source)
139
+ end
140
+
141
+ # Returns a copy of the dataset with the selected fields changed.
142
+ def select(*fields)
143
+ fields = fields.first if fields.size == 1
144
+ dup_merge(:select => fields)
145
+ end
146
+
147
+ # Returns a copy of the dataset with the order changed.
148
+ def order(*order)
149
+ dup_merge(:order => order)
150
+ end
151
+
152
+ DESC_ORDER_REGEXP = /(.*)\sDESC/.freeze
153
+
154
+ def reverse_order(order)
155
+ order.map do |f|
156
+ if f.to_s =~ DESC_ORDER_REGEXP
157
+ $1
158
+ else
159
+ f.DESC
160
+ end
161
+ end
162
+ end
163
+
164
+ # Returns a copy of the dataset with the where conditions changed.
165
+ def where(*where)
166
+ if where.size == 1
167
+ where = where.first
168
+ if @opts[:where] && @opts[:where].is_a?(Hash) && where.is_a?(Hash)
169
+ where = @opts[:where].merge(where)
170
+ end
171
+ end
172
+ dup_merge(:where => where)
173
+ end
174
+
175
+ LEFT_OUTER_JOIN = 'LEFT OUTER JOIN'.freeze
176
+ INNER_JOIN = 'INNER JOIN'.freeze
177
+ RIGHT_OUTER_JOIN = 'RIGHT OUTER JOIN'.freeze
178
+ FULL_OUTER_JOIN = 'FULL OUTER JOIN'.freeze
179
+
180
+ def join(table, cond)
181
+ dup_merge(:join_type => LEFT_OUTER_JOIN, :join_table => table,
182
+ :join_cond => cond)
183
+ end
184
+
185
+ alias_method :filter, :where
186
+ alias_method :all, :to_a
187
+ alias_method :enum_map, :map
188
+
189
+ #
190
+ def map(field_name = nil, &block)
191
+ if block
192
+ enum_map(&block)
193
+ elsif field_name
194
+ enum_map {|r| r[field_name]}
195
+ else
196
+ []
197
+ end
198
+ end
199
+
200
+ def hash_column(key_column, value_column)
201
+ inject({}) do |m, r|
202
+ m[r[key_column]] = r[value_column]
203
+ m
204
+ end
205
+ end
206
+
207
+ def <<(values)
208
+ insert(values)
209
+ end
210
+
211
+ def insert_multiple(array, &block)
212
+ if block
213
+ array.each {|i| insert(block[i])}
214
+ else
215
+ array.each {|i| insert(i)}
216
+ end
217
+ end
218
+
219
+ SELECT = "SELECT %s FROM %s".freeze
220
+ LIMIT = " LIMIT %s".freeze
221
+ ORDER = " ORDER BY %s".freeze
222
+ WHERE = " WHERE %s".freeze
223
+ JOIN_CLAUSE = " %s %s ON %s".freeze
224
+
225
+ EMPTY = ''.freeze
226
+
227
+ SPACE = ' '.freeze
228
+
229
+ def select_sql(opts = nil)
230
+ opts = opts ? @opts.merge(opts) : @opts
231
+
232
+ fields = opts[:select]
233
+ select_fields = fields ? field_list(fields) : WILDCARD
234
+ select_source = source_list(opts[:from])
235
+ sql = SELECT % [select_fields, select_source]
236
+
237
+ if join_type = opts[:join_type]
238
+ join_table = opts[:join_table]
239
+ join_cond = join_cond_list(opts[:join_cond], join_table)
240
+ sql << (JOIN_CLAUSE % [join_type, join_table, join_cond])
241
+ end
242
+
243
+ if where = opts[:where]
244
+ sql << (WHERE % where_list(where))
245
+ end
246
+
247
+ if order = opts[:order]
248
+ sql << (ORDER % order.join(COMMA_SEPARATOR))
249
+ end
250
+
251
+ if limit = opts[:limit]
252
+ sql << (LIMIT % limit)
253
+ end
254
+
255
+ sql
256
+ end
257
+
258
+ INSERT = "INSERT INTO %s (%s) VALUES (%s)".freeze
259
+ INSERT_EMPTY = "INSERT INTO %s DEFAULT VALUES".freeze
260
+
261
+ def insert_sql(values, opts = nil)
262
+ opts = opts ? @opts.merge(opts) : @opts
263
+
264
+ if values.nil? || values.empty?
265
+ INSERT_EMPTY % opts[:from]
266
+ else
267
+ field_list = []
268
+ value_list = []
269
+ values.each do |k, v|
270
+ field_list << k
271
+ value_list << literal(v)
272
+ end
273
+
274
+ INSERT % [
275
+ opts[:from],
276
+ field_list.join(COMMA_SEPARATOR),
277
+ value_list.join(COMMA_SEPARATOR)]
278
+ end
279
+ end
280
+
281
+ UPDATE = "UPDATE %s SET %s".freeze
282
+ SET_FORMAT = "%s = %s".freeze
283
+
284
+ def update_sql(values, opts = nil)
285
+ opts = opts ? @opts.merge(opts) : @opts
286
+
287
+ set_list = values.map {|kv| SET_FORMAT % [kv[0], literal(kv[1])]}.
288
+ join(COMMA_SEPARATOR)
289
+ update_clause = UPDATE % [opts[:from], set_list]
290
+
291
+ where = opts[:where]
292
+ where_clause = where ? WHERE % where_list(where) : EMPTY
293
+
294
+ [update_clause, where_clause].join(SPACE)
295
+ end
296
+
297
+ DELETE = "DELETE FROM %s".freeze
298
+
299
+ def delete_sql(opts = nil)
300
+ opts = opts ? @opts.merge(opts) : @opts
301
+
302
+ delete_source = opts[:from]
303
+
304
+ where = opts[:where]
305
+ where_clause = where ? WHERE % where_list(where) : EMPTY
306
+
307
+ [DELETE % delete_source, where_clause].join(SPACE)
308
+ end
309
+
310
+ COUNT = "COUNT(*)".freeze
311
+ SELECT_COUNT = {:select => COUNT, :order => nil}.freeze
312
+
313
+ def count_sql(opts = nil)
314
+ select_sql(opts ? opts.merge(SELECT_COUNT) : SELECT_COUNT)
315
+ end
316
+
317
+ # aggregates
318
+ def min(field)
319
+ select(field.MIN).first[:min]
320
+ end
321
+
322
+ def max(field)
323
+ select(field.MAX).first[:max]
324
+ end
325
+ end
326
+ end
327
+
328
+ class Symbol
329
+ def DESC
330
+ "#{to_s} DESC"
331
+ end
332
+
333
+ def AS(target)
334
+ "#{field_name} AS #{target}"
335
+ end
336
+
337
+ def MIN; "MIN(#{to_field_name})"; end
338
+ def MAX; "MAX(#{to_field_name})"; end
339
+
340
+ AS_REGEXP = /(.*)___(.*)/.freeze
341
+ AS_FORMAT = "%s AS %s".freeze
342
+ DOUBLE_UNDERSCORE = '__'.freeze
343
+ PERIOD = '.'.freeze
344
+
345
+ def to_field_name
346
+ s = to_s
347
+ if s =~ AS_REGEXP
348
+ s = AS_FORMAT % [$1, $2]
349
+ end
350
+ s.split(DOUBLE_UNDERSCORE).join(PERIOD)
351
+ end
352
+
353
+ def ALL
354
+ "#{to_s}.*"
355
+ end
356
+ end
357
+
@@ -0,0 +1,237 @@
1
+ #require 'rubygems'
2
+ require 'metaid'
3
+
4
+ module Sequel
5
+ class Model
6
+ @@db = nil
7
+
8
+ def self.db; @@db; end
9
+ def self.db=(db); @@db = db; end
10
+
11
+ def self.table_name; @table_name; end
12
+ def self.set_table_name(t); @table_name = t; end
13
+
14
+ def self.dataset
15
+ return @dataset if @dataset
16
+ if !table_name
17
+ raise RuntimeError, "Table name not specified for class #{self}."
18
+ elsif !db
19
+ raise RuntimeError, "No database connected."
20
+ end
21
+ @dataset = db[table_name]
22
+ @dataset.record_class = self
23
+ @dataset
24
+ end
25
+ def self.set_dataset(ds); @dataset = ds; @dataset.record_class = self; end
26
+
27
+ def self.cache_by(column, expiration)
28
+ @cache_column = column
29
+
30
+ prefix = "#{name}.#{column}."
31
+ define_method(:cache_key) do
32
+ prefix + @values[column].to_s
33
+ end
34
+
35
+ define_method("find_by_#{column}".to_sym) do |arg|
36
+ key = cache_key
37
+ rec = CACHE[key]
38
+ if !rec
39
+ rec = find(column => arg)
40
+ CACHE.set(key, rec, expiration)
41
+ end
42
+ rec
43
+ end
44
+
45
+ alias_method :delete, :delete_and_invalidate_cache
46
+ alias_method :set, :set_and_update_cache
47
+ end
48
+
49
+ def self.cache_column
50
+ @cache_column
51
+ end
52
+
53
+ def self.primary_key; @primary_key ||= :id; end
54
+ def self.set_primary_key(k); @primary_key = k; end
55
+
56
+ def self.schema(name = nil, &block)
57
+ name ||= table_name
58
+ @schema = Schema::Generator.new(name, &block)
59
+ set_table_name name
60
+ if @schema.primary_key_name
61
+ set_primary_key @schema.primary_key_name
62
+ end
63
+ end
64
+
65
+ def self.table_exists?
66
+ db.table_exists?(table_name)
67
+ end
68
+
69
+ def self.create_table
70
+ db.execute get_schema.create_sql
71
+ end
72
+
73
+ def self.drop_table
74
+ db.execute get_schema.drop_sql
75
+ end
76
+
77
+ def self.recreate_table
78
+ drop_table if table_exists?
79
+ create_table
80
+ end
81
+
82
+ def self.get_schema
83
+ @schema
84
+ end
85
+
86
+ ONE_TO_ONE_PROC = "proc {i = @values[:%s]; %s[i] if i}".freeze
87
+ ID_POSTFIX = "_id".freeze
88
+ FROM_DATASET = "db[%s]".freeze
89
+
90
+ def self.one_to_one(name, opts)
91
+ klass = opts[:class] ? opts[:class] : (FROM_DATASET % name.inspect)
92
+ key = opts[:key] || (name.to_s + ID_POSTFIX)
93
+ define_method name, &eval(ONE_TO_ONE_PROC % [key, klass])
94
+ end
95
+
96
+ ONE_TO_MANY_PROC = "proc {%s.filter(:%s => @pkey)}".freeze
97
+ ONE_TO_MANY_ORDER_PROC = "proc {%s.filter(:%s => @pkey).order(%s)}".freeze
98
+ def self.one_to_many(name, opts)
99
+ klass = opts[:class] ? opts[:class] :
100
+ (FROM_DATASET % (opts[:table] || name.inspect))
101
+ key = opts[:on]
102
+ order = opts[:order]
103
+ define_method name, &eval(
104
+ (order ? ONE_TO_MANY_ORDER_PROC : ONE_TO_MANY_PROC) %
105
+ [klass, key, order.inspect]
106
+ )
107
+ end
108
+
109
+ def self.get_hooks(key)
110
+ @hooks ||= {}
111
+ @hooks[key] ||= []
112
+ end
113
+
114
+ def self.has_hooks?(key)
115
+ !get_hooks(key).empty?
116
+ end
117
+
118
+ def run_hooks(key)
119
+ self.class.get_hooks(key).each {|h| instance_eval(&h)}
120
+ end
121
+
122
+ def self.before_delete(&block)
123
+ get_hooks(:before_delete).unshift(block)
124
+ end
125
+
126
+ def self.after_create(&block)
127
+ get_hooks(:after_create) << block
128
+ end
129
+
130
+ ############################################################################
131
+
132
+ attr_reader :values, :pkey
133
+
134
+ def model
135
+ self.class
136
+ end
137
+
138
+ def primary_key
139
+ model.primary_key
140
+ end
141
+
142
+ def initialize(values)
143
+ @values = values
144
+ @pkey = values[self.class.primary_key]
145
+ end
146
+
147
+ def exists?
148
+ model.filter(primary_key => @pkey).count == 1
149
+ end
150
+
151
+ def refresh
152
+ record = self.class.find(primary_key => @pkey)
153
+ record ? (@values = record.values) :
154
+ (raise RuntimeError, "Record not found")
155
+ self
156
+ end
157
+
158
+ def self.find(cond)
159
+ dataset.filter(cond).first # || (raise RuntimeError, "Record not found.")
160
+ end
161
+
162
+ def self.each(&block); dataset.each(&block); end
163
+ def self.all; dataset.all; end
164
+ def self.filter(*arg); dataset.filter(*arg); end
165
+ def self.first; dataset.first; end
166
+ def self.count; dataset.count; end
167
+ def self.map(column); dataset.map(column); end
168
+ def self.hash_column(column); dataset.hash_column(primary_key, column); end
169
+ def self.join(*args); dataset.join(*args); end
170
+ def self.lock(mode, &block); dataset.lock(mode, &block); end
171
+ def self.delete_all
172
+ if has_hooks?(:before_delete)
173
+ db.transaction {dataset.all.each {|r| r.delete}}
174
+ else
175
+ dataset.delete
176
+ end
177
+ end
178
+
179
+ def self.[](key)
180
+ find key.is_a?(Hash) ? key : {primary_key => key}
181
+ end
182
+
183
+ def self.create(values = nil)
184
+ db.transaction do
185
+ obj = find(primary_key => dataset.insert(values))
186
+ obj.run_hooks(:after_create)
187
+ obj
188
+ end
189
+ end
190
+
191
+ def delete
192
+ db.transaction do
193
+ run_hooks(:before_delete)
194
+ model.dataset.filter(primary_key => @pkey).delete
195
+ end
196
+ end
197
+
198
+ FIND_BY_REGEXP = /^find_by_(.*)/.freeze
199
+ FILTER_BY_REGEXP = /^filter_by_(.*)/.freeze
200
+
201
+ def self.method_missing(m, *args)
202
+ Thread.exclusive do
203
+ method_name = m.to_s
204
+ if method_name =~ FIND_BY_REGEXP
205
+ c = $1
206
+ meta_def(method_name) {|arg| find(c => arg)}
207
+ send(m, *args) if respond_to?(m)
208
+ elsif method_name =~ FILTER_BY_REGEXP
209
+ c = $1
210
+ meta_def(method_name) {|arg| filter(c => arg)}
211
+ send(m, *args) if respond_to?(m)
212
+ else
213
+ super
214
+ end
215
+ end
216
+ end
217
+
218
+ def db; @@db; end
219
+
220
+ def [](field); @values[field]; end
221
+
222
+ def ==(obj)
223
+ (obj.class == model) && (obj.pkey == @pkey)
224
+ end
225
+
226
+ def set(values)
227
+ model.dataset.filter(primary_key => @pkey).update(values)
228
+ @values.merge!(values)
229
+ end
230
+ end
231
+
232
+ def self.Model(table_name)
233
+ Class.new(Sequel::Model) do
234
+ meta_def(:inherited) {|c| c.set_table_name(table_name)}
235
+ end
236
+ end
237
+ end