sequel 4.33.0 → 4.34.0
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.
- 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
|