sequel 0.1.7 → 0.1.8
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 +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
|