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.
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