sequel 4.33.0 → 4.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +22 -0
- data/doc/release_notes/4.34.0.txt +86 -0
- data/doc/testing.rdoc +1 -0
- data/doc/validations.rdoc +12 -1
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/amalgalite.rb +1 -1
- data/lib/sequel/adapters/cubrid.rb +1 -1
- data/lib/sequel/adapters/do.rb +1 -1
- data/lib/sequel/adapters/ibmdb.rb +1 -1
- data/lib/sequel/adapters/jdbc.rb +1 -1
- data/lib/sequel/adapters/mock.rb +1 -1
- data/lib/sequel/adapters/mysql.rb +1 -1
- data/lib/sequel/adapters/mysql2.rb +1 -1
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +1 -1
- data/lib/sequel/adapters/swift.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +2 -2
- data/lib/sequel/connection_pool.rb +2 -0
- data/lib/sequel/connection_pool/sharded_single.rb +1 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +17 -4
- data/lib/sequel/connection_pool/single.rb +1 -1
- data/lib/sequel/connection_pool/threaded.rb +17 -4
- data/lib/sequel/database/misc.rb +5 -1
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/dataset/actions.rb +28 -15
- data/lib/sequel/extensions/columns_introspection.rb +1 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +87 -0
- data/lib/sequel/extensions/migration.rb +9 -7
- data/lib/sequel/extensions/pg_range.rb +73 -14
- data/lib/sequel/model/base.rb +2 -2
- data/lib/sequel/plugins/dataset_associations.rb +21 -1
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +7 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +14 -0
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/core/connection_pool_spec.rb +30 -3
- data/spec/core/database_spec.rb +2 -0
- data/spec/core/dataset_spec.rb +8 -0
- data/spec/extensions/dataset_associations_spec.rb +32 -0
- data/spec/extensions/duplicate_columns_handler_spec.rb +110 -0
- data/spec/extensions/pg_range_spec.rb +40 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +1 -1
- data/spec/extensions/validation_helpers_spec.rb +11 -0
- data/spec/integration/associations_test.rb +22 -8
- data/spec/integration/dataset_test.rb +10 -0
- data/spec/integration/eager_loader_test.rb +1 -1
- data/spec/integration/plugin_test.rb +3 -3
- data/spec/integration/spec_helper.rb +4 -0
- metadata +6 -2
@@ -47,7 +47,8 @@ module Sequel
|
|
47
47
|
if db_schema
|
48
48
|
h = {}
|
49
49
|
db_schema.each do |k, v|
|
50
|
-
|
50
|
+
default = v[:ruby_default]
|
51
|
+
h[k] = default if (default || !v[:default]) && !v[:primary_key] && !default.is_a?(Sequel::SQL::Expression)
|
51
52
|
end
|
52
53
|
@prepared_statements_column_defaults = h
|
53
54
|
end
|
@@ -7,7 +7,7 @@ module Sequel
|
|
7
7
|
# The first method is update_or_create, which updates an object if it
|
8
8
|
# exists in the database, or creates the object if it does not.
|
9
9
|
#
|
10
|
-
# You can call
|
10
|
+
# You can call update_or_create with a block:
|
11
11
|
#
|
12
12
|
# Album.update_or_create(:name=>'Hello') do |album|
|
13
13
|
# album.num_copies_sold = 1000
|
@@ -87,6 +87,7 @@ module Sequel
|
|
87
87
|
:min_length=>{:message=>lambda{|min| "is shorter than #{min} characters"}},
|
88
88
|
:not_null=>{:message=>lambda{"is not present"}},
|
89
89
|
:numeric=>{:message=>lambda{"is not a number"}},
|
90
|
+
:operator=>{:message=>lambda{|operator, rhs| "is not #{operator} #{rhs}"}},
|
90
91
|
:type=>{:message=>lambda{|klass| klass.is_a?(Array) ? "is not a valid #{klass.join(" or ").downcase}" : "is not a valid #{klass.to_s.downcase}"}},
|
91
92
|
:presence=>{:message=>lambda{"is not present"}},
|
92
93
|
:unique=>{:message=>lambda{'is already taken'}}
|
@@ -155,6 +156,12 @@ module Sequel
|
|
155
156
|
end
|
156
157
|
end
|
157
158
|
|
159
|
+
# Check attribute value(s) against a specified value and operation, e.g.
|
160
|
+
# validates_operator(:>, 3, :value) validates that value > 3.
|
161
|
+
def validates_operator(operator, rhs, atts, opts=OPTS)
|
162
|
+
validatable_attributes_for_type(:operator, atts, opts){|a,v,m| validation_error_message(m, operator, rhs) unless v.send(operator, rhs)}
|
163
|
+
end
|
164
|
+
|
158
165
|
# Validates for all of the model columns (or just the given columns)
|
159
166
|
# that the column value is an instance of the expected class based on
|
160
167
|
# the column's schema type.
|
data/lib/sequel/version.rb
CHANGED
@@ -5,7 +5,7 @@ module Sequel
|
|
5
5
|
MAJOR = 4
|
6
6
|
# The minor version of Sequel. Bumped for every non-patch level
|
7
7
|
# release, generally around once a month.
|
8
|
-
MINOR =
|
8
|
+
MINOR = 34
|
9
9
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
10
10
|
# releases that fix regressions from previous versions.
|
11
11
|
TINY = 0
|
@@ -3094,6 +3094,20 @@ describe 'PostgreSQL inet/cidr types' do
|
|
3094
3094
|
end
|
3095
3095
|
end
|
3096
3096
|
|
3097
|
+
describe 'PostgreSQL custom range types' do
|
3098
|
+
after do
|
3099
|
+
@db.run "DROP TYPE timerange";
|
3100
|
+
end
|
3101
|
+
|
3102
|
+
it "should allow registration and use" do
|
3103
|
+
@db = DB
|
3104
|
+
@db.run "CREATE TYPE timerange AS range (subtype = time)"
|
3105
|
+
@db.register_range_type('timerange')
|
3106
|
+
r = Sequel::SQLTime.create(10, 11, 12)..Sequel::SQLTime.create(11, 12, 13)
|
3107
|
+
@db.get(Sequel.pg_range(r, :timerange)).to_range.must_equal r
|
3108
|
+
end
|
3109
|
+
end if DB.server_version >= 90200 && DB.adapter_scheme == :postgres || DB.adapter_scheme == :jdbc
|
3110
|
+
|
3097
3111
|
describe 'PostgreSQL range types' do
|
3098
3112
|
before(:all) do
|
3099
3113
|
@db = DB
|
@@ -15,6 +15,7 @@ begin
|
|
15
15
|
rescue LoadError
|
16
16
|
end
|
17
17
|
|
18
|
+
Sequel::Database.extension :duplicate_column_handler if ENV['SEQUEL_DUPLICATE_COLUMN_HANDLER']
|
18
19
|
Sequel::Database.extension :columns_introspection if ENV['SEQUEL_COLUMNS_INTROSPECTION']
|
19
20
|
Sequel::Model.cache_associations = false if ENV['SEQUEL_NO_CACHE_ASSOCIATIONS']
|
20
21
|
Sequel.cache_anonymous_models = false
|
@@ -41,3 +42,8 @@ unless defined?(DB)
|
|
41
42
|
env_var = ENV.has_key?(env_var) ? env_var : 'SEQUEL_INTEGRATION_URL'
|
42
43
|
DB = Sequel.connect(ENV[env_var])
|
43
44
|
end
|
45
|
+
|
46
|
+
if dch = ENV['SEQUEL_DUPLICATE_COLUMNS_HANDLER']
|
47
|
+
DB.extension :duplicate_columns_handler
|
48
|
+
DB.opts[:on_duplicate_columns] = dch.to_sym unless dch.empty?
|
49
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
|
2
2
|
CONNECTION_POOL_DEFAULTS = {:pool_timeout=>5, :pool_sleep_time=>0.001, :max_connections=>4}
|
3
|
+
require 'sequel/connection_pool/sharded_threaded'
|
3
4
|
|
4
5
|
mock_db = lambda do |*a, &b|
|
5
6
|
db = Sequel.mock
|
@@ -296,10 +297,14 @@ ThreadedConnectionPoolSpecs = shared_description do
|
|
296
297
|
|
297
298
|
it "should raise a PoolTimeout error if a connection couldn't be acquired before timeout" do
|
298
299
|
q, q1 = Queue.new, Queue.new
|
299
|
-
|
300
|
+
db = mock_db.call(&@icpp)
|
301
|
+
db.opts[:name] = 'testing'
|
302
|
+
pool = Sequel::ConnectionPool.get_pool(db, @cp_opts.merge(:max_connections=>1, :pool_timeout=>0))
|
300
303
|
t = Thread.new{pool.hold{|c| q1.push nil; q.pop}}
|
301
304
|
q1.pop
|
302
|
-
proc{pool.hold{|c|}}.must_raise(Sequel::PoolTimeout)
|
305
|
+
e = proc{pool.hold{|c|}}.must_raise(Sequel::PoolTimeout)
|
306
|
+
e.message.must_include "name: testing"
|
307
|
+
e.message.must_include "server: default" if pool.is_a?(Sequel::ShardedThreadedConnectionPool)
|
303
308
|
q.push nil
|
304
309
|
t.join
|
305
310
|
end
|
@@ -492,7 +497,14 @@ describe "A connection pool with multiple servers" do
|
|
492
497
|
@pool.send(:preconnect)
|
493
498
|
i = 0
|
494
499
|
@pool.all_connections{|c1| i+=1}
|
495
|
-
i.must_equal
|
500
|
+
i.must_equal(@pool.max_size * 2)
|
501
|
+
end
|
502
|
+
|
503
|
+
it "should support preconnect method that immediately creates the maximum number of connections concurrently" do
|
504
|
+
@pool.send(:preconnect, true)
|
505
|
+
i = 0
|
506
|
+
@pool.all_connections{|c1| i+=1}
|
507
|
+
i.must_equal(@pool.max_size * 2)
|
496
508
|
end
|
497
509
|
|
498
510
|
it "#all_connections should return connections for all servers" do
|
@@ -769,6 +781,13 @@ describe "A single threaded pool with multiple servers" do
|
|
769
781
|
i.must_equal 2
|
770
782
|
end
|
771
783
|
|
784
|
+
it "should support preconnect method that immediately creates the maximum number of connections, ignoring concurrent param" do
|
785
|
+
@pool.send(:preconnect, true)
|
786
|
+
i = 0
|
787
|
+
@pool.all_connections{|c1| i+=1}
|
788
|
+
i.must_equal 2
|
789
|
+
end
|
790
|
+
|
772
791
|
it "#all_connections should return connections for all servers" do
|
773
792
|
@pool.hold{}
|
774
793
|
@pool.all_connections{|c1| c1.must_equal :default}
|
@@ -912,6 +931,14 @@ AllConnectionPoolClassesSpecs = shared_description do
|
|
912
931
|
i.must_equal p.max_size
|
913
932
|
end
|
914
933
|
|
934
|
+
it "should support preconnect method that immediately creates the maximum number of connections concurrently" do
|
935
|
+
p = @class.new(mock_db.call{123}, {})
|
936
|
+
p.send(:preconnect, true)
|
937
|
+
i = 0
|
938
|
+
p.all_connections{|c1| i+=1}
|
939
|
+
i.must_equal p.max_size
|
940
|
+
end
|
941
|
+
|
915
942
|
it "should be able to modify after_connect proc after the pool is created" do
|
916
943
|
a = []
|
917
944
|
p = @class.new(mock_db.call{123}, {})
|
data/spec/core/database_spec.rb
CHANGED
@@ -32,6 +32,8 @@ describe "A new Database" do
|
|
32
32
|
end
|
33
33
|
db = c.new(1 => 2, :logger => 3, :preconnect=>true)
|
34
34
|
db.pool.size.must_equal db.pool.max_size
|
35
|
+
db = c.new(1 => 2, :logger => 3, :preconnect=>:concurrently)
|
36
|
+
db.pool.size.must_equal db.pool.max_size
|
35
37
|
end
|
36
38
|
|
37
39
|
it "should handle the default string column size" do
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -1761,6 +1761,10 @@ describe "Dataset#to_hash" do
|
|
1761
1761
|
@d.to_hash(:b).must_equal(2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6})
|
1762
1762
|
end
|
1763
1763
|
|
1764
|
+
it "should accept an optional :hash parameter into which entries can be merged" do
|
1765
|
+
@d.to_hash(:a, :b, :hash => (tmp = {})).must_be_same_as(tmp)
|
1766
|
+
end
|
1767
|
+
|
1764
1768
|
it "should support using an array of columns as either the key or the value" do
|
1765
1769
|
@d.to_hash([:a, :b], :b).must_equal([1, 2] => 2, [3, 4] => 4, [5, 6] => 6)
|
1766
1770
|
@d.to_hash(:b, [:a, :b]).must_equal(2 => [1, 2], 4 => [3, 4], 6 => [5, 6])
|
@@ -1818,6 +1822,10 @@ describe "Dataset#to_hash_groups" do
|
|
1818
1822
|
@d.to_hash_groups([:a, :b]).must_equal([1, 2] => [{:a => 1, :b => 2}], [3, 4] => [{:a => 3, :b => 4}], [1, 6] => [{:a => 1, :b => 6}], [7, 4] => [{:a => 7, :b => 4}])
|
1819
1823
|
end
|
1820
1824
|
|
1825
|
+
it "should accept an optional :hash parameter into which entries can be merged" do
|
1826
|
+
@d.to_hash_groups(:a, :b, :hash => (tmp = {})).must_be_same_as(tmp)
|
1827
|
+
end
|
1828
|
+
|
1821
1829
|
it "should not call the row_proc if two arguments are given" do
|
1822
1830
|
@d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
|
1823
1831
|
@d.to_hash_groups(:a, :b).must_equal(1 => [2, 6], 3 => [4], 7 => [4])
|
@@ -90,6 +90,38 @@ describe "Sequel::Plugins::DatasetAssociations" do
|
|
90
90
|
ds.sql.must_equal "SELECT tags.* FROM tags WHERE (tags.id IN (SELECT albums_tags.tag_id FROM artists INNER JOIN albums ON (albums.artist_id = artists.id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id) WHERE (albums.artist_id IN (SELECT artists.id FROM artists))))"
|
91
91
|
end
|
92
92
|
|
93
|
+
it "should work for many_to_many associations with :dataset_association_join=>true" do
|
94
|
+
@Album.many_to_many :tags, :clone=>:tags, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
|
95
|
+
ds = @Album.tags
|
96
|
+
ds.must_be_kind_of(Sequel::Dataset)
|
97
|
+
ds.model.must_equal @Tag
|
98
|
+
ds.sql.must_equal "SELECT tags.*, albums_tags.foo FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (tags.id IN (SELECT albums_tags.tag_id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE ((albums_tags.album_id) IN (SELECT albums.id FROM albums))))"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should work for one_through_one associations with :dataset_association_join=>true" do
|
102
|
+
@Album.one_through_one :first_tag, :clone=>:first_tag, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
|
103
|
+
ds = @Album.first_tags
|
104
|
+
ds.must_be_kind_of(Sequel::Dataset)
|
105
|
+
ds.model.must_equal @Tag
|
106
|
+
ds.sql.must_equal "SELECT tags.*, albums_tags.foo FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (tags.id IN (SELECT albums_tags.tag_id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE ((albums_tags.album_id) IN (SELECT albums.id FROM albums))))"
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should work for many_through_many associations with :dataset_association_join=>true" do
|
110
|
+
@Artist.many_through_many :tags, :clone=>:tags, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
|
111
|
+
ds = @Artist.tags
|
112
|
+
ds.must_be_kind_of(Sequel::Dataset)
|
113
|
+
ds.model.must_equal @Tag
|
114
|
+
ds.sql.must_equal "SELECT tags.*, albums_tags.foo FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (tags.id IN (SELECT albums_tags.tag_id FROM artists INNER JOIN albums ON (albums.artist_id = artists.id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id) WHERE (albums.artist_id IN (SELECT artists.id FROM artists))))"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should work for one_through_many associations with :dataset_association_join=>true" do
|
118
|
+
@Artist.one_through_many :otag, :clone=>:otag, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
|
119
|
+
ds = @Artist.otags
|
120
|
+
ds.must_be_kind_of(Sequel::Dataset)
|
121
|
+
ds.model.must_equal @Tag
|
122
|
+
ds.sql.must_equal "SELECT tags.*, albums_tags.foo FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (tags.id IN (SELECT albums_tags.tag_id FROM artists INNER JOIN albums ON (albums.artist_id = artists.id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id) WHERE (albums.artist_id IN (SELECT artists.id FROM artists))))"
|
123
|
+
end
|
124
|
+
|
93
125
|
it "should work for pg_array_to_many associations" do
|
94
126
|
ds = @Artist.artist_tags
|
95
127
|
ds.must_be_kind_of(Sequel::Dataset)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
mod = shared_description do
|
4
|
+
it "should take action depending on :on_duplicate_columns if 2 or more columns have the same name" do
|
5
|
+
check(nil, @cols)
|
6
|
+
@warned.must_equal nil
|
7
|
+
|
8
|
+
check(:ignore, @cols)
|
9
|
+
@warned.must_equal nil
|
10
|
+
|
11
|
+
check(:warn, @cols)
|
12
|
+
@warned.must_equal("One or more duplicate columns present in #{@cols.inspect}")
|
13
|
+
|
14
|
+
proc{check(:raise, @cols)}.must_raise(Sequel::DuplicateColumnError)
|
15
|
+
|
16
|
+
cols = nil
|
17
|
+
check(proc{|cs| cols = cs; nil}, @cols)
|
18
|
+
@warned.must_equal nil
|
19
|
+
cols.must_equal @cols
|
20
|
+
|
21
|
+
cols = nil
|
22
|
+
check(proc{|cs| cols = cs; :ignore}, @cols)
|
23
|
+
@warned.must_equal nil
|
24
|
+
cols.must_equal @cols
|
25
|
+
|
26
|
+
cols = nil
|
27
|
+
proc{check(proc{|cs| cols = cs; :raise}, @cols)}.must_raise(Sequel::DuplicateColumnError)
|
28
|
+
cols.must_equal @cols
|
29
|
+
|
30
|
+
cols = nil
|
31
|
+
check(proc{|cs| cols = cs; :warn}, @cols)
|
32
|
+
@warned.must_equal("One or more duplicate columns present in #{@cols.inspect}")
|
33
|
+
cols.must_equal @cols
|
34
|
+
|
35
|
+
check(:raise, nil)
|
36
|
+
@warned.must_equal nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not raise error or warning if no columns have the same name" do
|
40
|
+
[nil, :ignore, :raise, :warn, proc{|cs| :raise}].each do |handler|
|
41
|
+
check(handler, @cols.uniq)
|
42
|
+
@warned.must_equal nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "Sequel::DuplicateColumnsHandler Database configuration" do
|
48
|
+
before do
|
49
|
+
@db = Sequel.mock
|
50
|
+
@db.extension(:duplicate_columns_handler)
|
51
|
+
@ds = @db[:things]
|
52
|
+
@cols = [:id, :name, :id]
|
53
|
+
@warned = nil
|
54
|
+
set_warned = @set_warned = proc{|m| @warned = m}
|
55
|
+
@ds.meta_def(:warn) do |message|
|
56
|
+
set_warned.call(message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def check(handler, cols)
|
61
|
+
@db.opts[:on_duplicate_columns] = handler
|
62
|
+
@set_warned.call(nil)
|
63
|
+
@ds.send(:columns=, cols)
|
64
|
+
end
|
65
|
+
|
66
|
+
include mod
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Sequel::DuplicateColumnsHandler Dataset configuration" do
|
70
|
+
before do
|
71
|
+
@ds = Sequel.mock[:things].extension!(:duplicate_columns_handler)
|
72
|
+
@cols = [:id, :name, :id]
|
73
|
+
@warned = nil
|
74
|
+
set_warned = @set_warned = proc{|m| @warned = m}
|
75
|
+
@ds.meta_def(:warn) do |message|
|
76
|
+
set_warned.call(message)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def check(handler, cols)
|
81
|
+
@set_warned.call(nil)
|
82
|
+
@ds.on_duplicate_columns(handler).send(:columns=, cols)
|
83
|
+
end
|
84
|
+
|
85
|
+
include mod
|
86
|
+
|
87
|
+
it "should use handlers passed as blocks to on_duplicate_columns" do
|
88
|
+
proc{@ds.on_duplicate_columns{:raise}.send(:columns=, @cols)}.must_raise(Sequel::DuplicateColumnError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should raise an error if not providing either an argument or block to on_duplicate_columns" do
|
92
|
+
proc{@ds.on_duplicate_columns}.must_raise(Sequel::Error)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should raise an error if providing both an argument and block to on_duplicate_columns" do
|
96
|
+
proc{@ds.on_duplicate_columns(:raise){:raise}}.must_raise(Sequel::Error)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should warn by defaul if there is no database or dataset handler" do
|
100
|
+
@ds.send(:columns=, @cols)
|
101
|
+
@warned.must_equal("One or more duplicate columns present in #{@cols.inspect}")
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should fallback to database setting if there is no dataset-level handler" do
|
105
|
+
@ds.db.opts[:on_duplicate_columns] = :raise
|
106
|
+
proc{@ds.send(:columns=, @cols)}.must_raise(Sequel::DuplicateColumnError)
|
107
|
+
check(:ignore, @cols)
|
108
|
+
@warned.must_equal nil
|
109
|
+
end
|
110
|
+
end
|
@@ -166,6 +166,46 @@ describe "pg_range extension" do
|
|
166
166
|
Sequel::Postgres::PG_TYPES[331].call('[1,3)').must_be_kind_of(@R)
|
167
167
|
end
|
168
168
|
|
169
|
+
it "should support registering custom range types on a per-Database basis" do
|
170
|
+
@db.register_range_type('banana', :oid=>7865){|s| s}
|
171
|
+
@db.typecast_value(:banana, '[1,2]').class.must_equal(Sequel::Postgres::PGRange)
|
172
|
+
@db.fetch = [{:name=>'id', :db_type=>'banana'}]
|
173
|
+
@db.schema(:items).map{|e| e[1][:type]}.must_equal [:banana]
|
174
|
+
@db.conversion_procs.must_include(7865)
|
175
|
+
@db.respond_to?(:typecast_value_banana, true).must_equal true
|
176
|
+
|
177
|
+
db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
178
|
+
db.extend_datasets(Module.new{def supports_timestamp_timezones?; false; end; def supports_timestamp_usecs?; false; end})
|
179
|
+
db.extension(:pg_range)
|
180
|
+
db.fetch = [{:name=>'id', :db_type=>'banana'}]
|
181
|
+
db.schema(:items).map{|e| e[1][:type]}.must_equal [nil]
|
182
|
+
db.conversion_procs.wont_include(7865)
|
183
|
+
db.respond_to?(:typecast_value_banana, true).must_equal false
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should automatically look up the range and subtype oids when registering per-Database types" do
|
187
|
+
@db.fetch = [[{:rngsubtype=>21, :rngtypid=>7866}], [{:name=>'id', :db_type=>'banana'}]]
|
188
|
+
@db.register_range_type('banana', :subtype_typecast=>:integer)
|
189
|
+
@db.sqls.must_equal ["SELECT rngtypid, rngsubtype FROM pg_range INNER JOIN pg_type ON (pg_type.oid = pg_range.rngtypid) WHERE (typname = 'banana') LIMIT 1"]
|
190
|
+
@db.schema(:items).map{|e| e[1][:type]}.must_equal [:banana]
|
191
|
+
@db.conversion_procs[7866].call("[1,3)").must_be :==, (1...3)
|
192
|
+
@db.typecast_value(:banana, '[1,2]').must_be :==, (1..2)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should not automatically look up oids if given both subtype and range oids" do
|
196
|
+
@db.register_range_type('banana', :oid=>7866, :subtype_oid=>21)
|
197
|
+
@db.sqls.must_equal []
|
198
|
+
@db.conversion_procs[7866].call("[1,3)").must_be :==, (1...3)
|
199
|
+
@db.typecast_value(:banana, '[1,2]').must_be :==, (1..2)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should not automatically look up oids if given range oid and block" do
|
203
|
+
@db.register_range_type('banana', :oid=>7866){|s| s.to_i}
|
204
|
+
@db.sqls.must_equal []
|
205
|
+
@db.conversion_procs[7866].call("[1,3)").must_be :==, (1...3)
|
206
|
+
@db.typecast_value(:banana, '[1,2]').must_be :==, (1..2)
|
207
|
+
end
|
208
|
+
|
169
209
|
it "should return correct results for Database#schema_type_class" do
|
170
210
|
@db.schema_type_class(:int4range).must_equal Sequel::Postgres::PGRange
|
171
211
|
@db.schema_type_class(:integer).must_equal Integer
|
@@ -17,7 +17,7 @@ describe "prepared_statements_safe plugin" do
|
|
17
17
|
|
18
18
|
it "should set default values correctly" do
|
19
19
|
@c.prepared_statements_column_defaults.must_equal(:name=>nil, :i=>nil)
|
20
|
-
@c.instance_variable_set(:@db_schema, {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
|
20
|
+
@c.instance_variable_set(:@db_schema, {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}, :bar=>{:ruby_default=>Sequel::CURRENT_TIMESTAMP}})
|
21
21
|
Class.new(@c).prepared_statements_column_defaults.must_equal(:name=>'foo')
|
22
22
|
end
|
23
23
|
|
@@ -538,4 +538,15 @@ describe "Sequel::Plugins::ValidationHelpers" do
|
|
538
538
|
m.wont_be :valid?
|
539
539
|
DB.sqls.must_equal []
|
540
540
|
end
|
541
|
+
|
542
|
+
it "should support validates_operator" do
|
543
|
+
@c.set_validations{validates_operator(:>, 3, :value)}
|
544
|
+
@m.value = 1
|
545
|
+
@m.wont_be :valid?
|
546
|
+
@m.errors.full_messages.must_equal ['value is not > 3']
|
547
|
+
@m.value = 3
|
548
|
+
@m.wont_be :valid?
|
549
|
+
@m.value = 4
|
550
|
+
@m.must_be :valid?
|
551
|
+
end
|
541
552
|
end
|
@@ -393,15 +393,15 @@ end
|
|
393
393
|
|
394
394
|
FilterByAssociations = shared_description do
|
395
395
|
it "should handle association inner joins" do
|
396
|
-
@Artist.association_join(:albums).all.must_equal []
|
397
|
-
@Artist.association_join(:first_album).all.must_equal []
|
398
|
-
@Album.association_join(:artist).all.must_equal []
|
399
|
-
@Album.association_join(:tags).all.must_equal []
|
400
|
-
@Album.association_join(:alias_tags).all.must_equal []
|
401
|
-
@Tag.association_join(:albums).all.must_equal []
|
396
|
+
@Artist.association_join(:albums).select(Sequel.qualify(@Artist.first_source, @Artist.columns.first)).all.must_equal []
|
397
|
+
@Artist.association_join(:first_album).select(Sequel.qualify(@Artist.first_source, @Artist.columns.first)).all.must_equal []
|
398
|
+
@Album.association_join(:artist).select(Sequel.qualify(@Album.first_source, @Album.columns.first)).all.must_equal []
|
399
|
+
@Album.association_join(:tags).select(Sequel.qualify(@Album.first_source, @Album.columns.first)).all.must_equal []
|
400
|
+
@Album.association_join(:alias_tags).select(Sequel.qualify(@Album.first_source, @Album.columns.first)).all.must_equal []
|
401
|
+
@Tag.association_join(:albums).select(Sequel.qualify(@Tag.first_source, @Tag.columns.first)).all.must_equal []
|
402
402
|
unless @no_many_through_many
|
403
|
-
@Artist.association_join(:tags).all.must_equal []
|
404
|
-
@Artist.association_join(:first_tag).all.must_equal []
|
403
|
+
@Artist.association_join(:tags).select(Sequel.qualify(@Artist.first_source, @Artist.columns.first)).all.must_equal []
|
404
|
+
@Artist.association_join(:first_tag).select(Sequel.qualify(@Artist.first_source, @Artist.columns.first)).all.must_equal []
|
405
405
|
end
|
406
406
|
|
407
407
|
@album.update(:artist => @artist)
|
@@ -2067,6 +2067,20 @@ describe "Sequel::Model Simple Associations" do
|
|
2067
2067
|
proc{@artist.remove_album(@album.id)}.must_raise(Sequel::Error)
|
2068
2068
|
proc{@artist.remove_album(@album)}.must_raise(Sequel::Error)
|
2069
2069
|
end
|
2070
|
+
|
2071
|
+
it "should handle dataset associations with :dataset_associations_join options" do
|
2072
|
+
Album.many_to_many :tags, :right_key=>:tag_id, :select=>[Sequel.expr(:tags).*, :albums_tags__tag_id___atid], :dataset_associations_join=>true
|
2073
|
+
Artist.many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]], :select=>[Sequel.expr(:tags).*, :albums_tags__tag_id___atid, :albums__artist_id___aid], :dataset_associations_join=>true
|
2074
|
+
|
2075
|
+
Album.tags.order(:tags__name).first.must_equal nil
|
2076
|
+
Artist.tags.order(:tags__name).first.must_equal nil
|
2077
|
+
|
2078
|
+
@album.add_tag(@tag)
|
2079
|
+
@artist.add_album(@album)
|
2080
|
+
|
2081
|
+
Album.tags.order(:tags__name).first.must_equal Tag.load(:id=>@tag.id, :name=>"T", :atid=>@tag.id)
|
2082
|
+
Artist.tags.order(:tags__name).first.must_equal Tag.load(:id=>@tag.id, :name=>"T", :atid=>@tag.id, :aid=>@artist.id)
|
2083
|
+
end
|
2070
2084
|
end
|
2071
2085
|
|
2072
2086
|
describe "Sequel::Model Composite Key Associations" do
|