sequel 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +16 -0
- data/Rakefile +1 -1
- data/lib/sequel.rb +2 -2
- data/lib/sequel/database.rb +4 -3
- data/lib/sequel/dataset.rb +159 -54
- data/lib/sequel/model.rb +22 -13
- data/lib/sequel/mysql.rb +14 -10
- data/lib/sequel/postgres.rb +59 -34
- data/lib/sequel/sqlite.rb +3 -13
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
*0.1.1*
|
2
|
+
|
3
|
+
* More documentation for Dataset.
|
4
|
+
|
5
|
+
* Added Dataset#size as alias to Dataset#count.
|
6
|
+
|
7
|
+
* Changed Database#<< to call execute (instead of being an alias). Thus it will work for descendants as well.
|
8
|
+
|
9
|
+
* Fixed Sequel.open to accept variable arity.
|
10
|
+
|
11
|
+
* Refactored Model#refresh, Model.create. Removed Model#reload.
|
12
|
+
|
13
|
+
* Refactored Model hooks.
|
14
|
+
|
15
|
+
* Cleaned up Dataset API.
|
16
|
+
|
1
17
|
*0.1.0*
|
2
18
|
|
3
19
|
* Changed Database#create_table to only accept a block. Nobody's gonna use the other way.
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "sequel"
|
9
|
-
VERS = "0.1.
|
9
|
+
VERS = "0.1.1"
|
10
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
11
11
|
RDOC_OPTS = ['--quiet', '--title', "Sequel: Concise ORM for Ruby",
|
12
12
|
"--opname", "index.html",
|
data/lib/sequel.rb
CHANGED
@@ -20,8 +20,8 @@ module Sequel #:nodoc:
|
|
20
20
|
# The specified scheme determines the database class used, and the rest
|
21
21
|
# of the string specifies the connection options. For example:
|
22
22
|
# DB = Sequel.open 'sqlite:///blog.db'
|
23
|
-
def connect(
|
24
|
-
Database.connect(
|
23
|
+
def connect(*args)
|
24
|
+
Database.connect(*args)
|
25
25
|
end
|
26
26
|
|
27
27
|
alias_method :open, :connect
|
data/lib/sequel/database.rb
CHANGED
@@ -36,7 +36,8 @@ module Sequel
|
|
36
36
|
def execute(sql)
|
37
37
|
raise NotImplementedError
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
|
+
def <<(sql); execute(sql); end
|
40
41
|
|
41
42
|
# Acquires a database connection, yielding it to the passed block.
|
42
43
|
def synchronize(&block)
|
@@ -147,11 +148,11 @@ module Sequel
|
|
147
148
|
# The specified scheme determines the database class used, and the rest
|
148
149
|
# of the string specifies the connection options. For example:
|
149
150
|
# DB = Sequel.open 'sqlite:///blog.db'
|
150
|
-
def self.connect(conn_string)
|
151
|
+
def self.connect(conn_string, more_opts = nil)
|
151
152
|
uri = URI.parse(conn_string)
|
152
153
|
c = @@adapters[uri.scheme.to_sym]
|
153
154
|
raise SequelError, "Invalid database scheme" unless c
|
154
|
-
c.new(c.uri_to_options(uri))
|
155
|
+
c.new(c.uri_to_options(uri).merge(more_opts || {}))
|
155
156
|
end
|
156
157
|
end
|
157
158
|
end
|
data/lib/sequel/dataset.rb
CHANGED
@@ -26,7 +26,7 @@ module Sequel
|
|
26
26
|
include Enumerable
|
27
27
|
|
28
28
|
attr_reader :db, :opts
|
29
|
-
attr_accessor :
|
29
|
+
attr_accessor :model_class
|
30
30
|
|
31
31
|
# Constructs a new instance of a dataset with a database instance, initial
|
32
32
|
# options and an optional record class. Datasets are usually constructed by
|
@@ -37,29 +37,24 @@ module Sequel
|
|
37
37
|
#
|
38
38
|
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
39
39
|
# database adaptor should provide a descendant class of Sequel::Dataset.
|
40
|
-
def initialize(db, opts = nil,
|
40
|
+
def initialize(db, opts = nil, model_class = nil)
|
41
41
|
@db = db
|
42
42
|
@opts = opts || {}
|
43
|
-
@
|
43
|
+
@model_class = model_class
|
44
44
|
end
|
45
45
|
|
46
|
-
# Returns a new instance of the dataset with
|
46
|
+
# Returns a new instance of the dataset with with the give options merged.
|
47
47
|
def dup_merge(opts)
|
48
|
-
self.class.new(@db, @opts.merge(opts), @
|
48
|
+
self.class.new(@db, @opts.merge(opts), @model_class)
|
49
49
|
end
|
50
50
|
|
51
51
|
# Returns a dataset that fetches records as hashes (instead of model
|
52
52
|
# objects). If no record class is defined for the dataset, self is
|
53
53
|
# returned.
|
54
54
|
def naked
|
55
|
-
@
|
55
|
+
@model_class ? self.class.new(@db, opts || @opts.dup) : self
|
56
56
|
end
|
57
57
|
|
58
|
-
AS_REGEXP = /(.*)___(.*)/.freeze
|
59
|
-
AS_FORMAT = "%s AS %s".freeze
|
60
|
-
DOUBLE_UNDERSCORE = '__'.freeze
|
61
|
-
PERIOD = '.'.freeze
|
62
|
-
|
63
58
|
# Returns a valid SQL fieldname as a string. Field names specified as
|
64
59
|
# symbols can include double underscores to denote a dot separator, e.g.
|
65
60
|
# :posts__id will be converted into posts.id.
|
@@ -79,7 +74,8 @@ module Sequel
|
|
79
74
|
WILDCARD = '*'.freeze
|
80
75
|
COMMA_SEPARATOR = ", ".freeze
|
81
76
|
|
82
|
-
# Converts
|
77
|
+
# Converts an array of field names into a comma seperated string of
|
78
|
+
# field names. If the array is empty, a wildcard (*) is returned.
|
83
79
|
def field_list(fields)
|
84
80
|
if fields.empty?
|
85
81
|
WILDCARD
|
@@ -88,9 +84,11 @@ module Sequel
|
|
88
84
|
end
|
89
85
|
end
|
90
86
|
|
91
|
-
# Converts an array of sources into a comma separated list.
|
87
|
+
# Converts an array of sources names into into a comma separated list.
|
92
88
|
def source_list(source)
|
93
|
-
|
89
|
+
if source.nil? || source.empty?
|
90
|
+
raise SequelError, 'No source specified for query'
|
91
|
+
end
|
94
92
|
source.map {|i| i.is_a?(Dataset) ? i.to_table_reference : i}.
|
95
93
|
join(COMMA_SEPARATOR)
|
96
94
|
end
|
@@ -100,10 +98,21 @@ module Sequel
|
|
100
98
|
DATE_FORMAT = "DATE '%Y-%m-%d'".freeze
|
101
99
|
|
102
100
|
# Returns a literal representation of a value to be used as part
|
103
|
-
# of an SQL expression.
|
101
|
+
# of an SQL expression. The stock implementation supports literalization
|
102
|
+
# of String (with proper escaping to prevent SQL injections), numbers,
|
103
|
+
# Symbol (as field references), Array (as a list of literalized values),
|
104
|
+
# Time (as an SQL TIMESTAMP), Date (as an SQL DATE), Dataset (as a
|
105
|
+
# subquery) and nil (AS NULL).
|
106
|
+
#
|
107
|
+
# dataset.literal("abc'def") #=> "'abc''def'"
|
108
|
+
# dataset.literal(:items__id) #=> "items.id"
|
109
|
+
# dataset.literal([1, 2, 3]) => "(1, 2, 3)"
|
110
|
+
# dataset.literal(DB[:items]) => "(SELECT * FROM items)"
|
111
|
+
#
|
112
|
+
# If an unsupported object is given, an exception is raised.
|
104
113
|
def literal(v)
|
105
114
|
case v
|
106
|
-
when String: "'
|
115
|
+
when String: "'#{v.gsub(/'/, "''")}'"
|
107
116
|
when Integer, Float: v.to_s
|
108
117
|
when NilClass: NULL
|
109
118
|
when Symbol: v.to_field_name
|
@@ -112,12 +121,28 @@ module Sequel
|
|
112
121
|
when Date: v.strftime(DATE_FORMAT)
|
113
122
|
when Dataset: "(#{v.sql})"
|
114
123
|
else
|
115
|
-
raise SequelError, "can't express #{v.inspect}
|
124
|
+
raise SequelError, "can't express #{v.inspect} as a SQL literal"
|
116
125
|
end
|
117
126
|
end
|
118
127
|
|
119
128
|
AND_SEPARATOR = " AND ".freeze
|
120
129
|
|
130
|
+
# Formats an equality expression involving a left value and a right value.
|
131
|
+
# Equality expressions differ according to the class of the right value.
|
132
|
+
# The stock implementation supports Range (inclusive and exclusive), Array
|
133
|
+
# (as a list of values to compare against), Dataset (as a subquery to
|
134
|
+
# compare against), or a regular value.
|
135
|
+
#
|
136
|
+
# dataset.format_eq_expression('id', 1..20) #=>
|
137
|
+
# "(id >= 1 AND id <= 20)"
|
138
|
+
# dataset.format_eq_expression('id', [3,6,10]) #=>
|
139
|
+
# "(id IN (3, 6, 10))"
|
140
|
+
# dataset.format_eq_expression('id', DB[:items].select(:id)) #=>
|
141
|
+
# "(id IN (SELECT id FROM items))"
|
142
|
+
# dataset.format_eq_expression('id', nil) #=>
|
143
|
+
# "(id IS NULL)"
|
144
|
+
# dataset.format_eq_expression('id', 3) #=>
|
145
|
+
# "(id = 3)"
|
121
146
|
def format_eq_expression(left, right)
|
122
147
|
case right
|
123
148
|
when Range:
|
@@ -127,7 +152,7 @@ module Sequel
|
|
127
152
|
when Array:
|
128
153
|
"(#{left} IN (#{literal(right)}))"
|
129
154
|
when Dataset:
|
130
|
-
"(#{left} IN #{
|
155
|
+
"(#{left} IN (#{right.sql}))"
|
131
156
|
when NilClass:
|
132
157
|
"(#{left} IS NULL)"
|
133
158
|
else
|
@@ -135,6 +160,16 @@ module Sequel
|
|
135
160
|
end
|
136
161
|
end
|
137
162
|
|
163
|
+
# Formats an expression comprising a left value, a binary operator and a
|
164
|
+
# right value. The supported operators are :eql (=), :not (!=), :lt (<),
|
165
|
+
# :lte (<=), :gt (>), :gte (>=) and :like (LIKE operator). Examples:
|
166
|
+
#
|
167
|
+
# dataset.format_expression('price', :gte, 100) #=> "(price >= 100)"
|
168
|
+
# dataset.format_expression('id', :not, 30) #=> "NOT (id = 30)"
|
169
|
+
# dataset.format_expression('name', :like, 'abc%') #=>
|
170
|
+
# "(name LIKE 'abc%')"
|
171
|
+
#
|
172
|
+
# If an unsupported operator is given, an exception is raised.
|
138
173
|
def format_expression(left, op, right)
|
139
174
|
left = field_name(left)
|
140
175
|
case op
|
@@ -157,6 +192,8 @@ module Sequel
|
|
157
192
|
end
|
158
193
|
end
|
159
194
|
|
195
|
+
QUESTION_MARK = '?'.freeze
|
196
|
+
|
160
197
|
# Formats a where clause. If parenthesize is true, then the whole
|
161
198
|
# generated clause will be enclosed in a set of parentheses.
|
162
199
|
def expression_list(where, parenthesize = false)
|
@@ -166,8 +203,7 @@ module Sequel
|
|
166
203
|
fmt = where.map {|i| format_expression(i[0], :eql, i[1])}.
|
167
204
|
join(AND_SEPARATOR)
|
168
205
|
when Array:
|
169
|
-
fmt = where.shift
|
170
|
-
fmt.gsub!('?') {|i| literal(where.shift)}
|
206
|
+
fmt = where.shift.gsub(QUESTION_MARK) {literal(where.shift)}
|
171
207
|
when Proc:
|
172
208
|
fmt = where.to_expressions.map {|e| format_expression(e.left, e.op, e.right)}.
|
173
209
|
join(AND_SEPARATOR)
|
@@ -215,6 +251,12 @@ module Sequel
|
|
215
251
|
|
216
252
|
DESC_ORDER_REGEXP = /(.*)\sDESC/.freeze
|
217
253
|
|
254
|
+
# Inverts the given order by breaking it into a list of field references
|
255
|
+
# and inverting them.
|
256
|
+
#
|
257
|
+
# dataset.invert_order('id DESC') #=> "id"
|
258
|
+
# dataset.invert_order('category, price DESC') #=>
|
259
|
+
# "category DESC, price"
|
218
260
|
def invert_order(order)
|
219
261
|
new_order = []
|
220
262
|
order.each do |f|
|
@@ -234,7 +276,25 @@ module Sequel
|
|
234
276
|
|
235
277
|
# Returns a copy of the dataset with the given conditions imposed upon it.
|
236
278
|
# If the query has been grouped, then the conditions are imposed in the
|
237
|
-
# HAVING clause. If not, then they are imposed in the WHERE clause.
|
279
|
+
# HAVING clause. If not, then they are imposed in the WHERE clause. Filter
|
280
|
+
# accepts a Hash (formated into a list of equality expressions), an Array
|
281
|
+
# (formatted ala ActiveRecord conditions), a String (taken literally), or
|
282
|
+
# a block that is converted into expressions.
|
283
|
+
#
|
284
|
+
# dataset.filter(:id => 3).sql #=>
|
285
|
+
# "SELECT * FROM items WHERE (id = 3)"
|
286
|
+
# dataset.filter('price < ?', 100).sql #=>
|
287
|
+
# "SELECT * FROM items WHERE price < 100"
|
288
|
+
# dataset.filter('price < 100').sql #=>
|
289
|
+
# "SELECT * FROM items WHERE price < 100"
|
290
|
+
# dataset.filter {price < 100}.sql #=>
|
291
|
+
# "SELECT * FROM items WHERE (price < 100)"
|
292
|
+
#
|
293
|
+
# Multiple filter calls can be chained for scoping:
|
294
|
+
#
|
295
|
+
# software = dataset.filter(:category => 'software')
|
296
|
+
# software.filter {price < 100}.sql #=>
|
297
|
+
# "SELECT * FROM items WHERE (category = 'software') AND (price < 100)"
|
238
298
|
def filter(*cond, &block)
|
239
299
|
clause = (@opts[:group] ? :having : :where)
|
240
300
|
cond = cond.first if cond.size == 1
|
@@ -248,6 +308,10 @@ module Sequel
|
|
248
308
|
end
|
249
309
|
end
|
250
310
|
|
311
|
+
# Performs the inverse of Dataset#filter.
|
312
|
+
#
|
313
|
+
# dataset.exclude(:category => 'software').sql #=>
|
314
|
+
# "SELECT * FROM items WHERE NOT (category = 'software')"
|
251
315
|
def exclude(*cond, &block)
|
252
316
|
clause = (@opts[:group] ? :having : :where)
|
253
317
|
cond = cond.first if cond.size == 1
|
@@ -263,7 +327,7 @@ module Sequel
|
|
263
327
|
end
|
264
328
|
|
265
329
|
# Returns a copy of the dataset with the where conditions changed. Raises
|
266
|
-
# if the dataset has been grouped. See also #filter
|
330
|
+
# if the dataset has been grouped. See also #filter.
|
267
331
|
def where(*cond, &block)
|
268
332
|
if @opts[:group]
|
269
333
|
raise SequelError, "Can't specify a WHERE clause once the dataset has been grouped"
|
@@ -286,26 +350,27 @@ module Sequel
|
|
286
350
|
INNER_JOIN = 'INNER JOIN'.freeze
|
287
351
|
RIGHT_OUTER_JOIN = 'RIGHT OUTER JOIN'.freeze
|
288
352
|
FULL_OUTER_JOIN = 'FULL OUTER JOIN'.freeze
|
289
|
-
|
353
|
+
|
354
|
+
# Returns a joined dataset.
|
290
355
|
def join(table, expr)
|
291
356
|
expr = {expr => :id} unless expr.is_a?(Hash)
|
292
357
|
dup_merge(:join_type => LEFT_OUTER_JOIN, :join_table => table,
|
293
358
|
:join_cond => expr)
|
294
359
|
end
|
295
360
|
|
296
|
-
|
361
|
+
alias all to_a
|
297
362
|
|
298
|
-
|
363
|
+
# Maps field values for each record in the dataset (if a field name is
|
364
|
+
# given), or performs the stock mapping functionality of Enumerable.
|
299
365
|
def map(field_name = nil, &block)
|
300
|
-
if
|
301
|
-
|
302
|
-
elsif field_name
|
303
|
-
enum_map {|r| r[field_name]}
|
366
|
+
if field_name
|
367
|
+
super() {|r| r[field_name]}
|
304
368
|
else
|
305
|
-
|
369
|
+
super(&block)
|
306
370
|
end
|
307
371
|
end
|
308
372
|
|
373
|
+
# Returns a hash with one column used as key and another used as value.
|
309
374
|
def hash_column(key_column, value_column)
|
310
375
|
inject({}) do |m, r|
|
311
376
|
m[r[key_column]] = r[value_column]
|
@@ -313,10 +378,13 @@ module Sequel
|
|
313
378
|
end
|
314
379
|
end
|
315
380
|
|
381
|
+
# Inserts the given values into the table.
|
316
382
|
def <<(values)
|
317
383
|
insert(values)
|
318
384
|
end
|
319
385
|
|
386
|
+
# Inserts multiple values. If a block is given it is invoked for each
|
387
|
+
# item in the given array before inserting it.
|
320
388
|
def insert_multiple(array, &block)
|
321
389
|
if block
|
322
390
|
array.each {|i| insert(block[i])}
|
@@ -326,9 +394,10 @@ module Sequel
|
|
326
394
|
end
|
327
395
|
|
328
396
|
EMPTY = ''.freeze
|
329
|
-
|
330
397
|
SPACE = ' '.freeze
|
331
398
|
|
399
|
+
# Formats a SELECT statement using the given options and the dataset
|
400
|
+
# options.
|
332
401
|
def select_sql(opts = nil)
|
333
402
|
opts = opts ? @opts.merge(opts) : @opts
|
334
403
|
|
@@ -367,12 +436,19 @@ module Sequel
|
|
367
436
|
sql << " OFFSET #{offset}"
|
368
437
|
end
|
369
438
|
end
|
370
|
-
|
439
|
+
|
371
440
|
sql
|
372
441
|
end
|
373
|
-
|
374
|
-
|
375
|
-
|
442
|
+
alias sql select_sql
|
443
|
+
|
444
|
+
# Formats an INSERT statement using the given values. If a hash is given,
|
445
|
+
# the resulting statement includes field names. If no values are given,
|
446
|
+
# the resulting statement includes a DEFAULT VALUES clause.
|
447
|
+
#
|
448
|
+
# dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
|
449
|
+
# dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
450
|
+
# dataset.insert_sql(:a => 1, :b => 2) #=>
|
451
|
+
# 'INSERT INTO items (a, b) VALUES (1, 2)'
|
376
452
|
def insert_sql(*values)
|
377
453
|
if values.empty?
|
378
454
|
"INSERT INTO #{@opts[:from]} DEFAULT VALUES"
|
@@ -391,6 +467,10 @@ module Sequel
|
|
391
467
|
end
|
392
468
|
end
|
393
469
|
|
470
|
+
# Formats an UPDATE statement using the given values.
|
471
|
+
#
|
472
|
+
# dataset.update_sql(:price => 100, :category => 'software') #=>
|
473
|
+
# "UPDATE items SET price = 100, category = 'software'"
|
394
474
|
def update_sql(values, opts = nil)
|
395
475
|
opts = opts ? @opts.merge(opts) : @opts
|
396
476
|
|
@@ -400,7 +480,7 @@ module Sequel
|
|
400
480
|
raise SequelError, "Can't update a joined dataset"
|
401
481
|
end
|
402
482
|
|
403
|
-
set_list = values.map {|
|
483
|
+
set_list = values.map {|k, v| "#{k} = #{literal(v)}"}.
|
404
484
|
join(COMMA_SEPARATOR)
|
405
485
|
sql = "UPDATE #{@opts[:from]} SET #{set_list}"
|
406
486
|
|
@@ -411,11 +491,15 @@ module Sequel
|
|
411
491
|
sql
|
412
492
|
end
|
413
493
|
|
494
|
+
# Formats a DELETE statement using the given options and dataset options.
|
495
|
+
#
|
496
|
+
# dataset.filter {price >= 100}.delete_sql #=>
|
497
|
+
# "DELETE FROM items WHERE (price >= 100)"
|
414
498
|
def delete_sql(opts = nil)
|
415
499
|
opts = opts ? @opts.merge(opts) : @opts
|
416
500
|
|
417
501
|
if opts[:group]
|
418
|
-
raise SequelError, "Can't delete from a grouped dataset"
|
502
|
+
raise SequelError, "Can't delete from a grouped dataset"
|
419
503
|
elsif opts[:from].is_a?(Array) && opts[:from].size > 1
|
420
504
|
raise SequelError, "Can't delete from a joined dataset"
|
421
505
|
end
|
@@ -429,13 +513,27 @@ module Sequel
|
|
429
513
|
sql
|
430
514
|
end
|
431
515
|
|
432
|
-
|
433
|
-
|
516
|
+
# Returns the first record in the dataset.
|
517
|
+
def single_record(opts = nil)
|
518
|
+
each(opts) {|r| return r}
|
519
|
+
end
|
520
|
+
|
521
|
+
# Returns the first value of the first reecord in the dataset.
|
522
|
+
def single_value(opts = nil)
|
523
|
+
naked.each(opts) {|r| return r.values.first}
|
524
|
+
end
|
525
|
+
|
526
|
+
SELECT_COUNT = {:select => ["COUNT(*)"], :order => nil}.freeze
|
434
527
|
|
435
|
-
|
436
|
-
|
528
|
+
# Returns the number of records in the dataset.
|
529
|
+
def count
|
530
|
+
single_value(SELECT_COUNT).to_i
|
437
531
|
end
|
438
|
-
|
532
|
+
alias size count
|
533
|
+
|
534
|
+
# Returns a table reference for use in the FROM clause. If the dataset has
|
535
|
+
# only a :from option refering to a single table, only the table name is
|
536
|
+
# returned. Otherwise a subquery is returned.
|
439
537
|
def to_table_reference
|
440
538
|
if opts.keys == [:from] && opts[:from].size == 1
|
441
539
|
opts[:from].first.to_s
|
@@ -444,29 +542,33 @@ module Sequel
|
|
444
542
|
end
|
445
543
|
end
|
446
544
|
|
447
|
-
#
|
545
|
+
# Returns the minimum value for the given field.
|
448
546
|
def min(field)
|
449
|
-
select
|
547
|
+
single_value(:select => [field.MIN])
|
450
548
|
end
|
451
549
|
|
550
|
+
# Returns the maximum value for the given field.
|
452
551
|
def max(field)
|
453
|
-
select
|
552
|
+
single_value(:select => [field.MAX])
|
454
553
|
end
|
455
554
|
|
555
|
+
# Returns the sum for the given field.
|
456
556
|
def sum(field)
|
457
|
-
select
|
557
|
+
single_value(:select => [field.SUM])
|
458
558
|
end
|
459
559
|
|
560
|
+
# Returns the average value for the given field.
|
460
561
|
def avg(field)
|
461
|
-
select
|
562
|
+
single_value(:select => [field.AVG])
|
462
563
|
end
|
463
564
|
|
565
|
+
# Returns an EXISTS clause for the dataset.
|
566
|
+
#
|
567
|
+
# dataset.exists #=> "EXISTS (SELECT 1 FROM items)"
|
464
568
|
def exists(opts = nil)
|
465
569
|
"EXISTS (#{sql({:select => [1]}.merge(opts || {}))})"
|
466
570
|
end
|
467
571
|
|
468
|
-
LIMIT_1 = {:limit => 1}.freeze
|
469
|
-
|
470
572
|
# If given an integer, the dataset will contain only the first l results.
|
471
573
|
# If given a range, it will contain only those at offsets within that
|
472
574
|
# range. If a second argument is given, it is used as an offset.
|
@@ -485,7 +587,7 @@ module Sequel
|
|
485
587
|
# an array is returned with the first <i>num</i> records.
|
486
588
|
def first(num = 1)
|
487
589
|
if num == 1
|
488
|
-
|
590
|
+
single_record
|
489
591
|
else
|
490
592
|
limit(num).all
|
491
593
|
end
|
@@ -496,6 +598,10 @@ module Sequel
|
|
496
598
|
where(*conditions).first
|
497
599
|
end
|
498
600
|
|
601
|
+
# Returns the last records in the dataset by inverting the order. If no
|
602
|
+
# order is given, an exception is raised. If num is not given, the last
|
603
|
+
# record is returned. Otherwise an array is returned with the last
|
604
|
+
# <i>num</i> records.
|
499
605
|
def last(num = 1)
|
500
606
|
raise SequelError, 'No order specified' unless
|
501
607
|
@opts[:order] || (opts && opts[:order])
|
@@ -505,7 +611,7 @@ module Sequel
|
|
505
611
|
merge(opts ? opts.merge(l) : l)
|
506
612
|
|
507
613
|
if num == 1
|
508
|
-
|
614
|
+
single_record(opts)
|
509
615
|
else
|
510
616
|
dup_merge(opts).all
|
511
617
|
end
|
@@ -514,7 +620,7 @@ module Sequel
|
|
514
620
|
# Deletes all records in the dataset one at a time by invoking the destroy
|
515
621
|
# method of the associated model class.
|
516
622
|
def destroy
|
517
|
-
raise SequelError, 'Dataset not associated with model' unless @
|
623
|
+
raise SequelError, 'Dataset not associated with model' unless @model_class
|
518
624
|
|
519
625
|
count = 0
|
520
626
|
@db.transaction {each {|r| count += 1; r.destroy}}
|
@@ -543,14 +649,13 @@ class Symbol
|
|
543
649
|
def AVG; "avg(#{to_field_name})"; end
|
544
650
|
|
545
651
|
AS_REGEXP = /(.*)___(.*)/.freeze
|
546
|
-
AS_FORMAT = "%s AS %s".freeze
|
547
652
|
DOUBLE_UNDERSCORE = '__'.freeze
|
548
653
|
PERIOD = '.'.freeze
|
549
654
|
|
550
655
|
def to_field_name
|
551
656
|
s = to_s
|
552
657
|
if s =~ AS_REGEXP
|
553
|
-
s =
|
658
|
+
s = "#{$1} AS #{$2}"
|
554
659
|
end
|
555
660
|
s.split(DOUBLE_UNDERSCORE).join(PERIOD)
|
556
661
|
end
|
data/lib/sequel/model.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rubygems'
|
1
2
|
require 'metaid'
|
2
3
|
|
3
4
|
module Sequel
|
@@ -22,14 +23,14 @@ module Sequel
|
|
22
23
|
raise RuntimeError, "No database connected."
|
23
24
|
end
|
24
25
|
@dataset = db[table_name]
|
25
|
-
@dataset.
|
26
|
+
@dataset.model_class = self
|
26
27
|
@dataset
|
27
28
|
end
|
28
29
|
|
29
30
|
def self.set_dataset(ds)
|
30
31
|
@db = ds.db
|
31
32
|
@dataset = ds
|
32
|
-
@dataset.
|
33
|
+
@dataset.model_class = self
|
33
34
|
end
|
34
35
|
|
35
36
|
def self.cache_by(column, expiration)
|
@@ -124,11 +125,19 @@ module Sequel
|
|
124
125
|
def run_hooks(key)
|
125
126
|
self.class.get_hooks(key).each {|h| instance_eval(&h)}
|
126
127
|
end
|
127
|
-
|
128
|
+
|
129
|
+
def self.before_create(&block)
|
130
|
+
get_hooks(:before_create).unshift(block)
|
131
|
+
end
|
132
|
+
|
128
133
|
def self.before_destroy(&block)
|
129
134
|
get_hooks(:before_destroy).unshift(block)
|
130
135
|
end
|
131
136
|
|
137
|
+
def self.after_destroy(&block)
|
138
|
+
get_hooks(:after_destroy).unshift(block)
|
139
|
+
end
|
140
|
+
|
132
141
|
def self.after_create(&block)
|
133
142
|
get_hooks(:after_create) << block
|
134
143
|
end
|
@@ -161,8 +170,7 @@ module Sequel
|
|
161
170
|
end
|
162
171
|
|
163
172
|
def refresh
|
164
|
-
|
165
|
-
record ? (@values = record.values) :
|
173
|
+
@values = self.class.dataset.naked[primary_key => @pkey] ||
|
166
174
|
(raise RuntimeError, "Record not found")
|
167
175
|
self
|
168
176
|
end
|
@@ -185,8 +193,8 @@ module Sequel
|
|
185
193
|
|
186
194
|
def self.create(values = nil)
|
187
195
|
db.transaction do
|
188
|
-
obj =
|
189
|
-
obj.
|
196
|
+
obj = new(values || {})
|
197
|
+
obj.save
|
190
198
|
obj
|
191
199
|
end
|
192
200
|
end
|
@@ -225,11 +233,6 @@ module Sequel
|
|
225
233
|
|
226
234
|
def db; self.class.db; end
|
227
235
|
|
228
|
-
def reload
|
229
|
-
temp = self.class[@pkey]
|
230
|
-
@values = self.class.dataset.naked[primary_key => @pkey]
|
231
|
-
end
|
232
|
-
|
233
236
|
def [](field); @values[field]; end
|
234
237
|
|
235
238
|
def []=(field, value); @values[field] = value; end
|
@@ -247,12 +250,18 @@ module Sequel
|
|
247
250
|
def id; @values[:id]; end
|
248
251
|
|
249
252
|
def save
|
253
|
+
run_hooks(:before_save)
|
250
254
|
if @pkey
|
255
|
+
# run_hooks(:before_update)
|
251
256
|
model.dataset.filter(primary_key => @pkey).update(@values)
|
257
|
+
# run_hooks(:after_update)
|
252
258
|
else
|
259
|
+
# run_hooks(:before_create)
|
253
260
|
@pkey = model.dataset.insert(@values)
|
254
|
-
|
261
|
+
refresh
|
262
|
+
# run_hooks(:after_create)
|
255
263
|
end
|
264
|
+
run_hooks(:after_save)
|
256
265
|
end
|
257
266
|
|
258
267
|
def ==(obj)
|
data/lib/sequel/mysql.rb
CHANGED
@@ -56,10 +56,6 @@ module Sequel
|
|
56
56
|
self
|
57
57
|
end
|
58
58
|
|
59
|
-
def first_record(opts = nil)
|
60
|
-
query_first(select_sql(opts), true)
|
61
|
-
end
|
62
|
-
|
63
59
|
def count(opts = nil)
|
64
60
|
query_single_value(count_sql(opts)).to_i
|
65
61
|
end
|
@@ -76,12 +72,12 @@ module Sequel
|
|
76
72
|
@db.execute_affected(delete_sql(opts))
|
77
73
|
end
|
78
74
|
|
79
|
-
def query_each(sql,
|
75
|
+
def query_each(sql, use_model_class = false)
|
80
76
|
@db.synchronize do
|
81
77
|
result = @db.execute(sql)
|
82
78
|
begin
|
83
|
-
if
|
84
|
-
result.each_hash {|r| yield @
|
79
|
+
if use_model_class && @model_class
|
80
|
+
result.each_hash {|r| yield @model_class.new(r)}
|
85
81
|
else
|
86
82
|
result.each_hash {|r| yield r}
|
87
83
|
end
|
@@ -92,12 +88,20 @@ module Sequel
|
|
92
88
|
self
|
93
89
|
end
|
94
90
|
|
95
|
-
def
|
91
|
+
def single_record(opts = nil)
|
92
|
+
query_single(select_sql(opts), true)
|
93
|
+
end
|
94
|
+
|
95
|
+
def single_value(opts = nil)
|
96
|
+
query_single_value(select_sql(opts))
|
97
|
+
end
|
98
|
+
|
99
|
+
def query_single(sql, use_model_class = false)
|
96
100
|
@db.synchronize do
|
97
101
|
result = @db.execute(sql)
|
98
102
|
begin
|
99
|
-
if
|
100
|
-
@
|
103
|
+
if use_model_class && @model_class
|
104
|
+
@model_class.new(result.fetch_hash)
|
101
105
|
else
|
102
106
|
result.fetch_hash
|
103
107
|
end
|
data/lib/sequel/postgres.rb
CHANGED
@@ -129,11 +129,12 @@ class String
|
|
129
129
|
TIME_REGEXP = /(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})/
|
130
130
|
|
131
131
|
def postgres_to_time
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
132
|
+
Time.parse(self)
|
133
|
+
# if self =~ TIME_REGEXP
|
134
|
+
# Time.local($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
|
135
|
+
# else
|
136
|
+
# nil
|
137
|
+
# end
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
@@ -215,8 +216,24 @@ module Sequel
|
|
215
216
|
end
|
216
217
|
|
217
218
|
class Dataset < Sequel::Dataset
|
219
|
+
TRUE = "'t'".freeze
|
220
|
+
FALSE = "'f'".freeze
|
221
|
+
|
218
222
|
def literal(v)
|
219
223
|
case v
|
224
|
+
# when String: "'%s'" % v.gsub(/'/, "''")
|
225
|
+
# when Integer, Float: v.to_s
|
226
|
+
# when NilClass: NULL
|
227
|
+
# when Symbol: v.to_field_name
|
228
|
+
# when Array: v.empty? ? NULL : v.map {|i| literal(i)}.join(COMMA_SEPARATOR)
|
229
|
+
# when Time: v.strftime(TIMESTAMP_FORMAT)
|
230
|
+
# when Date: v.strftime(DATE_FORMAT)
|
231
|
+
# when Dataset: "(#{v.sql})"
|
232
|
+
# when true: TRUE
|
233
|
+
# when false: FALSE
|
234
|
+
# else
|
235
|
+
# raise SequelError, "can't express #{v.inspect}:#{v.class} as a SQL literal"
|
236
|
+
# end
|
220
237
|
when String, Fixnum, Float, TrueClass, FalseClass: PGconn.quote(v)
|
221
238
|
else
|
222
239
|
super
|
@@ -241,10 +258,6 @@ module Sequel
|
|
241
258
|
self
|
242
259
|
end
|
243
260
|
|
244
|
-
def first_record(opts = nil)
|
245
|
-
query_first(select_sql(opts), true)
|
246
|
-
end
|
247
|
-
|
248
261
|
FOR_UPDATE = ' FOR UPDATE'.freeze
|
249
262
|
FOR_SHARE = ' FOR SHARE'.freeze
|
250
263
|
|
@@ -301,10 +314,6 @@ module Sequel
|
|
301
314
|
end
|
302
315
|
end
|
303
316
|
|
304
|
-
def count(opts = nil)
|
305
|
-
query_single_value(count_sql(opts)).to_i
|
306
|
-
end
|
307
|
-
|
308
317
|
def insert(*values)
|
309
318
|
@db.execute_insert(insert_sql(*values), @opts[:from])
|
310
319
|
end
|
@@ -333,25 +342,20 @@ module Sequel
|
|
333
342
|
end
|
334
343
|
end
|
335
344
|
|
336
|
-
def
|
337
|
-
|
338
|
-
result = @db.execute(sql)
|
339
|
-
begin
|
340
|
-
conv = row_converter(result, use_record_class)
|
341
|
-
all = []
|
342
|
-
result.each {|r| all << conv[r]}
|
343
|
-
ensure
|
344
|
-
result.clear
|
345
|
-
end
|
346
|
-
all
|
347
|
-
end
|
345
|
+
def single_record(opts = nil)
|
346
|
+
query_single(select_sql(opts), true)
|
348
347
|
end
|
349
|
-
|
350
|
-
def
|
348
|
+
|
349
|
+
def single_value(opts = nil)
|
350
|
+
query_single_value(select_sql(opts))
|
351
|
+
end
|
352
|
+
|
353
|
+
def query_each(sql, use_model_class = false, &block)
|
351
354
|
@db.synchronize do
|
352
355
|
result = @db.execute(sql)
|
353
356
|
begin
|
354
|
-
|
357
|
+
# each_row(result, use_model_class, &block)
|
358
|
+
conv = row_converter(result, use_model_class)
|
355
359
|
result.each {|r| yield conv[r]}
|
356
360
|
ensure
|
357
361
|
result.clear
|
@@ -359,12 +363,13 @@ module Sequel
|
|
359
363
|
end
|
360
364
|
end
|
361
365
|
|
362
|
-
def
|
366
|
+
def query_single(sql, use_model_class = false)
|
363
367
|
@db.synchronize do
|
364
368
|
result = @db.execute(sql)
|
365
369
|
begin
|
366
370
|
row = nil
|
367
|
-
|
371
|
+
# each_row(result, use_model_class) {|r| row = r}
|
372
|
+
conv = row_converter(result, use_model_class)
|
368
373
|
result.each {|r| row = conv.call(r)}
|
369
374
|
ensure
|
370
375
|
result.clear
|
@@ -378,22 +383,42 @@ module Sequel
|
|
378
383
|
result = @db.execute(sql)
|
379
384
|
begin
|
380
385
|
value = result.getvalue(0, 0)
|
386
|
+
if value
|
387
|
+
value = value.send(PG_TYPES[result.type(0)])
|
388
|
+
end
|
381
389
|
ensure
|
382
390
|
result.clear
|
383
391
|
end
|
384
392
|
value
|
385
393
|
end
|
386
394
|
end
|
395
|
+
|
396
|
+
def each_row(result, use_model_class)
|
397
|
+
fields = result.fields.map {|s| s.to_sym}
|
398
|
+
types = (0..(result.num_fields - 1)).map {|idx| PG_TYPES[result.type(idx)]}
|
399
|
+
m_klass = use_model_class && @model_class
|
400
|
+
result.each do |row|
|
401
|
+
hashed_row = {}
|
402
|
+
row.each_index do |cel_index|
|
403
|
+
column = row[cel_index]
|
404
|
+
if column && types[cel_index]
|
405
|
+
column = column.send(types[cel_index])
|
406
|
+
end
|
407
|
+
hashed_row[fields[cel_index]] = column
|
408
|
+
end
|
409
|
+
yield m_klass ? m_klass.new(hashed_row) : hashed_row
|
410
|
+
end
|
411
|
+
end
|
387
412
|
|
388
413
|
COMMA = ','.freeze
|
389
414
|
|
390
415
|
@@converters_mutex = Mutex.new
|
391
416
|
@@converters = {}
|
392
417
|
|
393
|
-
def row_converter(result,
|
418
|
+
def row_converter(result, use_model_class)
|
394
419
|
fields = result.fields.map {|s| s.to_sym}
|
395
420
|
types = (0..(result.num_fields - 1)).map {|idx| result.type(idx)}
|
396
|
-
klass =
|
421
|
+
klass = use_model_class ? @model_class : nil
|
397
422
|
|
398
423
|
# create result signature and memoize the converter
|
399
424
|
sig = fields.join(COMMA) + types.join(COMMA) + klass.to_s
|
@@ -403,7 +428,7 @@ module Sequel
|
|
403
428
|
end
|
404
429
|
|
405
430
|
CONVERT = "lambda {|r| {%s}}".freeze
|
406
|
-
|
431
|
+
CONVERT_MODEL_CLASS = "lambda {|r| %2$s.new(%1$s)}".freeze
|
407
432
|
|
408
433
|
CONVERT_FIELD = '%s => r[%d]'.freeze
|
409
434
|
CONVERT_FIELD_TRANSLATE = '%s => ((t = r[%d]) ? t.%s : nil)'.freeze
|
@@ -419,7 +444,7 @@ module Sequel
|
|
419
444
|
kvs << (translate_fn ? CONVERT_FIELD_TRANSLATE : CONVERT_FIELD) %
|
420
445
|
[field.inspect, idx, translate_fn]
|
421
446
|
end
|
422
|
-
s = (klass ?
|
447
|
+
s = (klass ? CONVERT_MODEL_CLASS : CONVERT) %
|
423
448
|
[kvs.join(COMMA), klass]
|
424
449
|
eval(s)
|
425
450
|
end
|
data/lib/sequel/sqlite.rb
CHANGED
@@ -43,7 +43,7 @@ module Sequel
|
|
43
43
|
@pool.hold {|conn| conn.get_first_value(sql)}
|
44
44
|
end
|
45
45
|
|
46
|
-
def result_set(sql,
|
46
|
+
def result_set(sql, model_class, &block)
|
47
47
|
@pool.hold do |conn|
|
48
48
|
conn.query(sql) do |result|
|
49
49
|
columns = result.columns
|
@@ -51,7 +51,7 @@ module Sequel
|
|
51
51
|
result.each do |values|
|
52
52
|
row = {}
|
53
53
|
column_count.times {|i| row[columns[i].to_sym] = values[i]}
|
54
|
-
block.call(
|
54
|
+
block.call(model_class ? model_class.new(row) : row)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -68,20 +68,10 @@ module Sequel
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def each(opts = nil, &block)
|
71
|
-
@db.result_set(select_sql(opts), @
|
71
|
+
@db.result_set(select_sql(opts), @model_class, &block)
|
72
72
|
self
|
73
73
|
end
|
74
74
|
|
75
|
-
LIMIT_1 = {:limit => 1}.freeze
|
76
|
-
|
77
|
-
def first_record(opts = nil)
|
78
|
-
@db.result_set(select_sql(opts), @record_class) {|r| return r}
|
79
|
-
end
|
80
|
-
|
81
|
-
def count(opts = nil)
|
82
|
-
@db.single_value(count_sql(opts)).to_i
|
83
|
-
end
|
84
|
-
|
85
75
|
def insert(*values)
|
86
76
|
@db.synchronize do
|
87
77
|
@db.execute_insert insert_sql(*values)
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: sequel
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2007-04-
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2007-04-28 00:00:00 +03:00
|
8
8
|
summary: Concise ORM for Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|