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