sequel 4.33.0 → 4.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +22 -0
  3. data/doc/release_notes/4.34.0.txt +86 -0
  4. data/doc/testing.rdoc +1 -0
  5. data/doc/validations.rdoc +12 -1
  6. data/lib/sequel/adapters/ado.rb +1 -1
  7. data/lib/sequel/adapters/amalgalite.rb +1 -1
  8. data/lib/sequel/adapters/cubrid.rb +1 -1
  9. data/lib/sequel/adapters/do.rb +1 -1
  10. data/lib/sequel/adapters/ibmdb.rb +1 -1
  11. data/lib/sequel/adapters/jdbc.rb +1 -1
  12. data/lib/sequel/adapters/mock.rb +1 -1
  13. data/lib/sequel/adapters/mysql.rb +1 -1
  14. data/lib/sequel/adapters/mysql2.rb +1 -1
  15. data/lib/sequel/adapters/odbc.rb +1 -1
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +1 -1
  18. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  19. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  20. data/lib/sequel/adapters/sqlite.rb +1 -1
  21. data/lib/sequel/adapters/swift.rb +1 -1
  22. data/lib/sequel/adapters/tinytds.rb +2 -2
  23. data/lib/sequel/connection_pool.rb +2 -0
  24. data/lib/sequel/connection_pool/sharded_single.rb +1 -1
  25. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -4
  26. data/lib/sequel/connection_pool/single.rb +1 -1
  27. data/lib/sequel/connection_pool/threaded.rb +17 -4
  28. data/lib/sequel/database/misc.rb +5 -1
  29. data/lib/sequel/dataset.rb +4 -0
  30. data/lib/sequel/dataset/actions.rb +28 -15
  31. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  32. data/lib/sequel/extensions/duplicate_columns_handler.rb +87 -0
  33. data/lib/sequel/extensions/migration.rb +9 -7
  34. data/lib/sequel/extensions/pg_range.rb +73 -14
  35. data/lib/sequel/model/base.rb +2 -2
  36. data/lib/sequel/plugins/dataset_associations.rb +21 -1
  37. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  38. data/lib/sequel/plugins/update_or_create.rb +1 -1
  39. data/lib/sequel/plugins/validation_helpers.rb +7 -0
  40. data/lib/sequel/version.rb +1 -1
  41. data/spec/adapters/postgres_spec.rb +14 -0
  42. data/spec/adapters/spec_helper.rb +6 -0
  43. data/spec/core/connection_pool_spec.rb +30 -3
  44. data/spec/core/database_spec.rb +2 -0
  45. data/spec/core/dataset_spec.rb +8 -0
  46. data/spec/extensions/dataset_associations_spec.rb +32 -0
  47. data/spec/extensions/duplicate_columns_handler_spec.rb +110 -0
  48. data/spec/extensions/pg_range_spec.rb +40 -0
  49. data/spec/extensions/prepared_statements_safe_spec.rb +1 -1
  50. data/spec/extensions/validation_helpers_spec.rb +11 -0
  51. data/spec/integration/associations_test.rb +22 -8
  52. data/spec/integration/dataset_test.rb +10 -0
  53. data/spec/integration/eager_loader_test.rb +1 -1
  54. data/spec/integration/plugin_test.rb +3 -3
  55. data/spec/integration/spec_helper.rb +4 -0
  56. 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
- h[k] = v[:ruby_default] if (v[:ruby_default] || !v[:default]) && !v[:primary_key]
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 create_or_update with a block:
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.
@@ -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 = 33
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
- pool = Sequel::ConnectionPool.get_pool(mock_db.call(&@icpp), @cp_opts.merge(:max_connections=>1, :pool_timeout=>0))
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 @pool.max_size * 2
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}, {})
@@ -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
@@ -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