sequel 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +50 -28
- data/README +1 -1
- data/Rakefile +13 -3
- data/lib/sequel/database.rb +2 -2
- data/lib/sequel/dataset.rb +180 -674
- data/lib/sequel/dataset/dataset_convenience.rb +132 -0
- data/lib/sequel/dataset/dataset_sql.rb +564 -0
- data/lib/sequel/dbi.rb +5 -4
- data/lib/sequel/model.rb +2 -2
- data/lib/sequel/mysql.rb +5 -48
- data/lib/sequel/odbc.rb +7 -12
- data/lib/sequel/postgres.rb +22 -73
- data/lib/sequel/sqlite.rb +54 -15
- data/spec/adapters/sqlite_spec.rb +104 -0
- data/spec/connection_pool_spec.rb +270 -0
- data/spec/core_ext_spec.rb +127 -0
- data/spec/database_spec.rb +366 -0
- data/spec/dataset_spec.rb +1449 -0
- data/spec/expressions_spec.rb +151 -0
- metadata +12 -2
data/lib/sequel/dbi.rb
CHANGED
@@ -44,10 +44,11 @@ module Sequel
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
47
|
+
def fetch_rows(sql, &block)
|
48
48
|
@db.synchronize do
|
49
|
-
s = @db.execute
|
49
|
+
s = @db.execute sql
|
50
50
|
begin
|
51
|
+
@columns = stmt.column_names.map {|c| c.to_sym}
|
51
52
|
s.fetch {|r| yield hash_row(s, r)}
|
52
53
|
ensure
|
53
54
|
s.finish rescue nil
|
@@ -57,8 +58,8 @@ module Sequel
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def hash_row(stmt, row)
|
60
|
-
|
61
|
-
m[
|
61
|
+
@columns.inject({}) do |m, c|
|
62
|
+
m[c] = row.shift
|
62
63
|
m
|
63
64
|
end
|
64
65
|
end
|
data/lib/sequel/model.rb
CHANGED
@@ -26,14 +26,14 @@ module Sequel
|
|
26
26
|
raise SequelError, "Database not specified for #{self}."
|
27
27
|
end
|
28
28
|
@dataset = db[table_name]
|
29
|
-
@dataset.
|
29
|
+
@dataset.set_model(self)
|
30
30
|
@dataset
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.set_dataset(ds)
|
34
34
|
@db = ds.db
|
35
35
|
@dataset = ds
|
36
|
-
@dataset.
|
36
|
+
@dataset.set_model(self)
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.cache_by(column, expiration)
|
data/lib/sequel/mysql.rb
CHANGED
@@ -48,15 +48,6 @@ module Sequel
|
|
48
48
|
end
|
49
49
|
|
50
50
|
class Dataset < Sequel::Dataset
|
51
|
-
def each(opts = nil, &block)
|
52
|
-
query_each(select_sql(opts), true, &block)
|
53
|
-
self
|
54
|
-
end
|
55
|
-
|
56
|
-
def count(opts = nil)
|
57
|
-
query_single_value(count_sql(opts)).to_i
|
58
|
-
end
|
59
|
-
|
60
51
|
def insert(*values)
|
61
52
|
@db.execute_insert(insert_sql(*values))
|
62
53
|
end
|
@@ -69,15 +60,12 @@ module Sequel
|
|
69
60
|
@db.execute_affected(delete_sql(opts))
|
70
61
|
end
|
71
62
|
|
72
|
-
def
|
63
|
+
def fetch_rows(sql)
|
73
64
|
@db.synchronize do
|
74
65
|
result = @db.execute(sql)
|
75
66
|
begin
|
76
|
-
|
77
|
-
|
78
|
-
else
|
79
|
-
result.each_hash {|r| yield r}
|
80
|
-
end
|
67
|
+
fetch_columns(result)
|
68
|
+
result.each_hash {|r| yield r}
|
81
69
|
ensure
|
82
70
|
result.free
|
83
71
|
end
|
@@ -85,39 +73,8 @@ module Sequel
|
|
85
73
|
self
|
86
74
|
end
|
87
75
|
|
88
|
-
def
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
def single_value(opts = nil)
|
93
|
-
query_single_value(select_sql(opts))
|
94
|
-
end
|
95
|
-
|
96
|
-
def query_single(sql, use_model_class = false)
|
97
|
-
@db.synchronize do
|
98
|
-
result = @db.execute(sql)
|
99
|
-
begin
|
100
|
-
if use_model_class && @model_class
|
101
|
-
row = @model_class.new(result.fetch_hash)
|
102
|
-
else
|
103
|
-
row = result.fetch_hash
|
104
|
-
end
|
105
|
-
ensure
|
106
|
-
result.free
|
107
|
-
end
|
108
|
-
row
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def query_single_value(sql)
|
113
|
-
@db.synchronize do
|
114
|
-
result = @db.execute(sql)
|
115
|
-
begin
|
116
|
-
return result.fetch_hash.values[0]
|
117
|
-
ensure
|
118
|
-
result.free
|
119
|
-
end
|
120
|
-
end
|
76
|
+
def fetch_columns(result)
|
77
|
+
@columns = result.fetch_fields.map {|c| c.name.to_sym}
|
121
78
|
end
|
122
79
|
end
|
123
80
|
end
|
data/lib/sequel/odbc.rb
CHANGED
@@ -44,12 +44,13 @@ module Sequel
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
47
|
+
def fetch_rows(sql, &block)
|
48
48
|
@db.synchronize do
|
49
|
-
s = @db.execute select_sql(
|
49
|
+
s = @db.execute select_sql(sql)
|
50
50
|
begin
|
51
|
-
|
52
|
-
|
51
|
+
@columns = s.columns(true).map {|c| c.name.to_sym}
|
52
|
+
rows = s.fetch_all
|
53
|
+
rows.each {|row| yield hash_row(row)}
|
53
54
|
ensure
|
54
55
|
s.drop unless s.nil? rescue nil
|
55
56
|
end
|
@@ -57,16 +58,10 @@ module Sequel
|
|
57
58
|
self
|
58
59
|
end
|
59
60
|
|
60
|
-
def
|
61
|
-
columns = stmt.columns(true).map {|c| c.name.to_sym}
|
62
|
-
rows = stmt.fetch_all
|
63
|
-
rows.each {|row| yield hash_row(stmt, columns, row)}
|
64
|
-
end
|
65
|
-
|
66
|
-
def hash_row(stmt, columns, row)
|
61
|
+
def hash_row(row)
|
67
62
|
hash = {}
|
68
63
|
row.each_with_index do |v, idx|
|
69
|
-
hash[columns[idx]] = convert_odbc_value(v)
|
64
|
+
hash[@columns[idx]] = convert_odbc_value(v)
|
70
65
|
end
|
71
66
|
hash
|
72
67
|
end
|
data/lib/sequel/postgres.rb
CHANGED
@@ -277,19 +277,6 @@ module Sequel
|
|
277
277
|
|
278
278
|
def literal(v)
|
279
279
|
case v
|
280
|
-
# when String: "'%s'" % v.gsub(/'/, "''")
|
281
|
-
# when Integer, Float: v.to_s
|
282
|
-
# when NilClass: NULL
|
283
|
-
# when Symbol: v.to_field_name
|
284
|
-
# when Array: v.empty? ? NULL : v.map {|i| literal(i)}.join(COMMA_SEPARATOR)
|
285
|
-
# when Time: v.strftime(TIMESTAMP_FORMAT)
|
286
|
-
# when Date: v.strftime(DATE_FORMAT)
|
287
|
-
# when Dataset: "(#{v.sql})"
|
288
|
-
# when true: TRUE
|
289
|
-
# when false: FALSE
|
290
|
-
# else
|
291
|
-
# raise SequelError, "can't express #{v.inspect}:#{v.class} as a SQL literal"
|
292
|
-
# end
|
293
280
|
when String, Fixnum, Float, TrueClass, FalseClass: PGconn.quote(v)
|
294
281
|
else
|
295
282
|
super
|
@@ -309,11 +296,6 @@ module Sequel
|
|
309
296
|
end
|
310
297
|
end
|
311
298
|
|
312
|
-
def each(opts = nil, &block)
|
313
|
-
query_each(select_sql(opts), true, &block)
|
314
|
-
self
|
315
|
-
end
|
316
|
-
|
317
299
|
FOR_UPDATE = ' FOR UPDATE'.freeze
|
318
300
|
FOR_SHARE = ' FOR SHARE'.freeze
|
319
301
|
|
@@ -328,19 +310,28 @@ module Sequel
|
|
328
310
|
end
|
329
311
|
|
330
312
|
def for_update
|
331
|
-
|
313
|
+
clone_merge(:lock => :update)
|
332
314
|
end
|
333
315
|
|
334
316
|
def for_share
|
335
|
-
|
317
|
+
clone_merge(:lock => :share)
|
336
318
|
end
|
337
319
|
|
338
320
|
EXPLAIN = 'EXPLAIN '.freeze
|
321
|
+
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
339
322
|
QUERY_PLAN = 'QUERY PLAN'.to_sym
|
340
323
|
|
341
324
|
def explain(opts = nil)
|
342
325
|
analysis = []
|
343
|
-
|
326
|
+
fetch_rows(EXPLAIN + select_sql(opts)) do |r|
|
327
|
+
analysis << r[QUERY_PLAN]
|
328
|
+
end
|
329
|
+
analysis.join("\r\n")
|
330
|
+
end
|
331
|
+
|
332
|
+
def analyze(opts = nil)
|
333
|
+
analysis = []
|
334
|
+
fetch_rows(EXPLAIN_ANALYZE + select_sql(opts)) do |r|
|
344
335
|
analysis << r[QUERY_PLAN]
|
345
336
|
end
|
346
337
|
analysis.join("\r\n")
|
@@ -399,20 +390,11 @@ module Sequel
|
|
399
390
|
end
|
400
391
|
end
|
401
392
|
|
402
|
-
def
|
403
|
-
query_single(select_sql(opts), true)
|
404
|
-
end
|
405
|
-
|
406
|
-
def single_value(opts = nil)
|
407
|
-
query_single_value(select_sql(opts))
|
408
|
-
end
|
409
|
-
|
410
|
-
def query_each(sql, use_model_class = false, &block)
|
393
|
+
def fetch_rows(sql, &block)
|
411
394
|
@db.synchronize do
|
412
395
|
result = @db.execute(sql)
|
413
396
|
begin
|
414
|
-
|
415
|
-
conv = row_converter(result, use_model_class)
|
397
|
+
conv = row_converter(result)
|
416
398
|
result.each {|r| yield conv[r]}
|
417
399
|
ensure
|
418
400
|
result.clear
|
@@ -420,54 +402,25 @@ module Sequel
|
|
420
402
|
end
|
421
403
|
end
|
422
404
|
|
423
|
-
def query_single(sql, use_model_class = false)
|
424
|
-
@db.synchronize do
|
425
|
-
result = @db.execute(sql)
|
426
|
-
begin
|
427
|
-
row = nil
|
428
|
-
conv = row_converter(result, use_model_class)
|
429
|
-
result.each {|r| row = conv.call(r)}
|
430
|
-
ensure
|
431
|
-
result.clear
|
432
|
-
end
|
433
|
-
row
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
def query_single_value(sql)
|
438
|
-
@db.synchronize do
|
439
|
-
result = @db.execute(sql)
|
440
|
-
begin
|
441
|
-
value = result.getvalue(0, 0)
|
442
|
-
if value
|
443
|
-
value = value.send(PG_TYPES[result.type(0)])
|
444
|
-
end
|
445
|
-
ensure
|
446
|
-
result.clear
|
447
|
-
end
|
448
|
-
value
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
405
|
@@converters_mutex = Mutex.new
|
453
406
|
@@converters = {}
|
454
407
|
|
455
|
-
def row_converter(result
|
408
|
+
def row_converter(result)
|
456
409
|
fields = []; translators = []
|
457
410
|
result.fields.each_with_index do |f, idx|
|
458
411
|
fields << f.to_sym
|
459
412
|
translators << PG_TYPES[result.type(idx)]
|
460
413
|
end
|
461
|
-
|
414
|
+
@columns = fields
|
462
415
|
|
463
416
|
# create result signature and memoize the converter
|
464
|
-
sig = [fields, translators
|
417
|
+
sig = [fields, translators].hash
|
465
418
|
@@converters_mutex.synchronize do
|
466
|
-
@@converters[sig] ||= compile_converter(fields, translators
|
419
|
+
@@converters[sig] ||= compile_converter(fields, translators)
|
467
420
|
end
|
468
421
|
end
|
469
422
|
|
470
|
-
def compile_converter(fields, translators
|
423
|
+
def compile_converter(fields, translators)
|
471
424
|
used_fields = []
|
472
425
|
kvs = []
|
473
426
|
fields.each_with_index do |field, idx|
|
@@ -475,16 +428,12 @@ module Sequel
|
|
475
428
|
used_fields << field
|
476
429
|
|
477
430
|
if translator = translators[idx]
|
478
|
-
kvs << "
|
431
|
+
kvs << ":\"#{field}\" => ((t = r[#{idx}]) ? t.#{translator} : nil)"
|
479
432
|
else
|
480
|
-
kvs << "
|
433
|
+
kvs << ":\"#{field}\" => r[#{idx}]"
|
481
434
|
end
|
482
435
|
end
|
483
|
-
|
484
|
-
eval("lambda {|r| #{klass}.new(#{kvs.join(COMMA_SEPARATOR)})}")
|
485
|
-
else
|
486
|
-
eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
|
487
|
-
end
|
436
|
+
eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
|
488
437
|
end
|
489
438
|
end
|
490
439
|
end
|
data/lib/sequel/sqlite.rb
CHANGED
@@ -44,20 +44,52 @@ module Sequel
|
|
44
44
|
@pool.hold {|conn| conn.get_first_value(sql)}
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
47
|
+
def query(sql, &block)
|
48
48
|
@logger.info(sql) if @logger
|
49
|
-
@pool.hold
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
row = {}
|
55
|
-
column_count.times {|i| row[columns[i].to_sym] = values[i]}
|
56
|
-
block.call(model_class ? model_class.new(row) : row)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
49
|
+
@pool.hold {|conn| conn.query(sql, &block)}
|
50
|
+
end
|
51
|
+
|
52
|
+
def pragma_get(name)
|
53
|
+
single_value("PRAGMA #{name};")
|
60
54
|
end
|
55
|
+
|
56
|
+
def pragma_set(name, value)
|
57
|
+
execute("PRAGMA #{name} = #{value};")
|
58
|
+
end
|
59
|
+
|
60
|
+
AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze
|
61
|
+
|
62
|
+
def auto_vacuum
|
63
|
+
AUTO_VACUUM[pragma_get(:auto_vacuum)]
|
64
|
+
end
|
65
|
+
|
66
|
+
def auto_vacuum=(value)
|
67
|
+
value = AUTO_VACUUM.index(value) || (raise SequelError, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
|
68
|
+
pragma_set(:auto_vacuum, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze
|
72
|
+
|
73
|
+
def synchronous
|
74
|
+
SYNCHRONOUS[pragma_get(:synchronous)]
|
75
|
+
end
|
76
|
+
|
77
|
+
def synchronous=(value)
|
78
|
+
value = SYNCHRONOUS.index(value) || (raise SequelError, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
|
79
|
+
pragma_set(:synchronous, value)
|
80
|
+
end
|
81
|
+
|
82
|
+
TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
|
83
|
+
|
84
|
+
def temp_store
|
85
|
+
TEMP_STORE[pragma_get(:temp_store)]
|
86
|
+
end
|
87
|
+
|
88
|
+
def temp_store=(value)
|
89
|
+
value = TEMP_STORE.index(value) || (raise SequelError, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
|
90
|
+
pragma_set(:temp_store, value)
|
91
|
+
end
|
92
|
+
|
61
93
|
end
|
62
94
|
|
63
95
|
class Dataset < Sequel::Dataset
|
@@ -69,9 +101,16 @@ module Sequel
|
|
69
101
|
end
|
70
102
|
end
|
71
103
|
|
72
|
-
def
|
73
|
-
@db.
|
74
|
-
|
104
|
+
def fetch_rows(sql, &block)
|
105
|
+
@db.query(sql) do |result|
|
106
|
+
@columns = result.columns.map {|c| c.to_sym}
|
107
|
+
column_count = @columns.size
|
108
|
+
result.each do |values|
|
109
|
+
row = {}
|
110
|
+
column_count.times {|i| row[@columns[i]] = values[i]}
|
111
|
+
block.call(row)
|
112
|
+
end
|
113
|
+
end
|
75
114
|
end
|
76
115
|
|
77
116
|
def insert(*values)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../../lib/sequel/sqlite')
|
2
|
+
|
3
|
+
SQLITE_DB = Sequel('sqlite:/')
|
4
|
+
SQLITE_DB.create_table :items do
|
5
|
+
integer :id, :primary_key => true, :auto_increment => true
|
6
|
+
text :name
|
7
|
+
float :value
|
8
|
+
end
|
9
|
+
|
10
|
+
context "An SQLite database" do
|
11
|
+
setup do
|
12
|
+
@db = Sequel('sqlite:/')
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "should provide a list of existing tables" do
|
16
|
+
@db.tables.should == []
|
17
|
+
|
18
|
+
@db.create_table :testing do
|
19
|
+
text :name
|
20
|
+
end
|
21
|
+
@db.tables.should include(:testing)
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "should support getting pragma values" do
|
25
|
+
@db.pragma_get(:auto_vacuum).should == '0'
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "should support setting pragma values" do
|
29
|
+
@db.pragma_set(:auto_vacuum, '1')
|
30
|
+
@db.pragma_get(:auto_vacuum).should == '1'
|
31
|
+
end
|
32
|
+
|
33
|
+
specify "should support getting and setting the auto_vacuum pragma" do
|
34
|
+
@db.auto_vacuum = :full
|
35
|
+
@db.auto_vacuum.should == :full
|
36
|
+
@db.auto_vacuum = :none
|
37
|
+
@db.auto_vacuum.should == :none
|
38
|
+
|
39
|
+
proc {@db.auto_vacuum = :invalid}.should raise_error(SequelError)
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "should support getting and setting the synchronous pragma" do
|
43
|
+
@db.synchronous = :off
|
44
|
+
@db.synchronous.should == :off
|
45
|
+
@db.synchronous = :normal
|
46
|
+
@db.synchronous.should == :normal
|
47
|
+
@db.synchronous = :full
|
48
|
+
@db.synchronous.should == :full
|
49
|
+
|
50
|
+
proc {@db.synchronous = :invalid}.should raise_error(SequelError)
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "should support getting and setting the temp_store pragma" do
|
54
|
+
@db.temp_store = :default
|
55
|
+
@db.temp_store.should == :default
|
56
|
+
@db.temp_store = :file
|
57
|
+
@db.temp_store.should == :file
|
58
|
+
@db.temp_store = :memory
|
59
|
+
@db.temp_store.should == :memory
|
60
|
+
|
61
|
+
proc {@db.temp_store = :invalid}.should raise_error(SequelError)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "An SQLite dataset" do
|
66
|
+
setup do
|
67
|
+
@d = SQLITE_DB[:items]
|
68
|
+
@d.delete # remove all records
|
69
|
+
end
|
70
|
+
|
71
|
+
specify "should return the correct record count" do
|
72
|
+
@d.count.should == 0
|
73
|
+
@d << {:name => 'abc', :value => 1.23}
|
74
|
+
@d << {:name => 'abc', :value => 4.56}
|
75
|
+
@d << {:name => 'def', :value => 7.89}
|
76
|
+
@d.count.should == 3
|
77
|
+
end
|
78
|
+
|
79
|
+
specify "should return the last inserted id when inserting records" do
|
80
|
+
id = @d << {:name => 'abc', :value => 1.23}
|
81
|
+
id.should == @d.first[:id]
|
82
|
+
end
|
83
|
+
|
84
|
+
specify "should update records correctly" do
|
85
|
+
@d << {:name => 'abc', :value => 1.23}
|
86
|
+
@d << {:name => 'abc', :value => 4.56}
|
87
|
+
@d << {:name => 'def', :value => 7.89}
|
88
|
+
@d.filter(:name => 'abc').update(:value => 5.3)
|
89
|
+
|
90
|
+
# the third record should stay the same
|
91
|
+
@d[:name => 'def'][:value].should == 7.89
|
92
|
+
@d.filter(:value => 5.3).count.should == 2
|
93
|
+
end
|
94
|
+
|
95
|
+
specify "should delete records correctly" do
|
96
|
+
@d << {:name => 'abc', :value => 1.23}
|
97
|
+
@d << {:name => 'abc', :value => 4.56}
|
98
|
+
@d << {:name => 'def', :value => 7.89}
|
99
|
+
@d.filter(:name => 'abc').delete
|
100
|
+
|
101
|
+
@d.count.should == 1
|
102
|
+
@d.first[:name].should == 'def'
|
103
|
+
end
|
104
|
+
end
|