sequel 4.11.0 → 4.12.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/Rakefile +1 -5
  4. data/doc/opening_databases.rdoc +5 -1
  5. data/doc/release_notes/4.12.0.txt +105 -0
  6. data/lib/sequel/adapters/jdbc.rb +1 -0
  7. data/lib/sequel/adapters/oracle.rb +1 -0
  8. data/lib/sequel/adapters/postgres.rb +17 -8
  9. data/lib/sequel/adapters/shared/cubrid.rb +2 -1
  10. data/lib/sequel/adapters/shared/db2.rb +1 -0
  11. data/lib/sequel/adapters/shared/mssql.rb +1 -0
  12. data/lib/sequel/adapters/shared/postgres.rb +23 -2
  13. data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -0
  14. data/lib/sequel/adapters/sqlite.rb +11 -5
  15. data/lib/sequel/database/query.rb +14 -1
  16. data/lib/sequel/dataset/prepared_statements.rb +2 -1
  17. data/lib/sequel/dataset/query.rb +48 -37
  18. data/lib/sequel/dataset/sql.rb +0 -39
  19. data/lib/sequel/extensions/pg_interval.rb +1 -1
  20. data/lib/sequel/extensions/pg_static_cache_updater.rb +11 -5
  21. data/lib/sequel/model/associations.rb +2 -2
  22. data/lib/sequel/plugins/auto_validations.rb +16 -4
  23. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  24. data/lib/sequel/plugins/nested_attributes.rb +72 -59
  25. data/lib/sequel/plugins/prepared_statements.rb +16 -5
  26. data/lib/sequel/plugins/prepared_statements_associations.rb +14 -0
  27. data/lib/sequel/sql.rb +2 -43
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/postgres_spec.rb +49 -20
  30. data/spec/bin_spec.rb +2 -2
  31. data/spec/core/dataset_spec.rb +18 -6
  32. data/spec/core/schema_spec.rb +2 -1
  33. data/spec/extensions/auto_validations_spec.rb +23 -2
  34. data/spec/extensions/nested_attributes_spec.rb +32 -1
  35. data/spec/extensions/pg_static_cache_updater_spec.rb +12 -0
  36. data/spec/extensions/prepared_statements_associations_spec.rb +17 -17
  37. data/spec/extensions/prepared_statements_spec.rb +11 -8
  38. data/spec/integration/plugin_test.rb +43 -0
  39. data/spec/integration/schema_test.rb +7 -0
  40. data/spec/model/eager_loading_spec.rb +9 -0
  41. metadata +4 -2
@@ -53,6 +53,20 @@ module Sequel
53
53
  opts.send(:cached_fetch, :prepared_statement) do
54
54
  unless opts[:instance_specific]
55
55
  ds, bv = _associated_dataset(opts, {}).unbind
56
+
57
+ f = ds.opts[:from]
58
+ if f && f.length == 1
59
+ s = ds.opts[:select]
60
+ if ds.opts[:join]
61
+ if opts.eager_loading_use_associated_key? && s && s.length == 1 && s.first.is_a?(SQL::ColumnAll)
62
+ table = s.first.table
63
+ ds = ds.select(*opts.associated_class.columns.map{|c| Sequel.identifier(c).qualify(table)})
64
+ end
65
+ elsif !s || s.empty?
66
+ ds = ds.select(*opts.associated_class.columns.map{|c| Sequel.identifier(c)})
67
+ end
68
+ end
69
+
56
70
  if bv.length != assoc_bv.length
57
71
  h = {}
58
72
  bv.each do |k,v|
data/lib/sequel/sql.rb CHANGED
@@ -1339,16 +1339,6 @@ module Sequel
1339
1339
  end
1340
1340
  end
1341
1341
 
1342
- # REMOVE411
1343
- class EmulatedFunction < Function
1344
- def self.new(name, *args)
1345
- Deprecation.deprecate("Sequel::SQL::EmulatedFunction", "Please use Sequel::SQL::Function.new!(name, args, :emulate=>true) to create an emulated SQL function")
1346
- Function.new!(name, args, :emulate=>true)
1347
- end
1348
-
1349
- to_s_method :emulated_function_sql
1350
- end
1351
-
1352
1342
  class GenericExpression
1353
1343
  include AliasMethods
1354
1344
  include BooleanMethods
@@ -1394,18 +1384,9 @@ module Sequel
1394
1384
  attr_reader :table_expr
1395
1385
 
1396
1386
  # Create an object with the given join_type and table expression.
1397
- def initialize(join_type, table, table_alias = nil)
1387
+ def initialize(join_type, table_expr)
1398
1388
  @join_type = join_type
1399
-
1400
- @table_expr = if table.is_a?(AliasedExpression)
1401
- table
1402
- # REMOVE411
1403
- elsif table_alias
1404
- Deprecation.deprecate("The table_alias argument to Sequel::SQL::JoinClause#initialize", "Please use a Sequel::SQL::AliasedExpression as the table argument instead.")
1405
- AliasedExpression.new(table, table_alias)
1406
- else
1407
- table
1408
- end
1389
+ @table_expr = table_expr
1409
1390
  end
1410
1391
 
1411
1392
  # The table/set related to the JOIN, without any alias.
@@ -1827,28 +1808,6 @@ module Sequel
1827
1808
  to_s_method :window_sql, '@opts'
1828
1809
  end
1829
1810
 
1830
- # REMOVE411
1831
- class WindowFunction < GenericExpression
1832
- # The function to use, should be an <tt>SQL::Function</tt>.
1833
- attr_reader :function
1834
-
1835
- # The window to use, should be an <tt>SQL::Window</tt>.
1836
- attr_reader :window
1837
-
1838
- def self.new(function, window)
1839
- Deprecation.deprecate("Sequel::SQL::WindowFunction", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
1840
- function.over(window)
1841
- end
1842
-
1843
- # Set the function and window.
1844
- def initialize(function, window)
1845
- Deprecation.deprecate("Sequel::SQL::WindowFunction", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
1846
- @function, @window = function, window
1847
- end
1848
-
1849
- to_s_method :window_function_sql, '@function, @window'
1850
- end
1851
-
1852
1811
  # A +Wrapper+ is a simple way to wrap an existing object so that it supports
1853
1812
  # the Sequel DSL.
1854
1813
  class Wrapper < GenericExpression
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 11
6
+ MINOR = 12
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -179,6 +179,19 @@ describe "A PostgreSQL database" do
179
179
  @db.server_version.should > 70000
180
180
  end
181
181
 
182
+ specify "should create a dataset using the VALUES clause via #values" do
183
+ @db.values([[1, 2], [3, 4]]).map([:column1, :column2]).should == [[1, 2], [3, 4]]
184
+ end
185
+
186
+ specify "should support ordering and limiting with #values" do
187
+ @db.values([[1, 2], [3, 4]]).reverse(:column2, :column1).limit(1).map([:column1, :column2]).should == [[3, 4]]
188
+ @db.values([[1, 2], [3, 4]]).reverse(:column2, :column1).offset(1).map([:column1, :column2]).should == [[1, 2]]
189
+ end
190
+
191
+ specify "should support subqueries with #values" do
192
+ @db.values([[1, 2]]).from_self.cross_join(@db.values([[3, 4]]).as(:x, [:c1, :c2])).map([:column1, :column2, :c1, :c2]).should == [[1, 2, 3, 4]]
193
+ end
194
+
182
195
  specify "should respect the :read_only option per-savepoint" do
183
196
  proc{@db.transaction{@db.transaction(:savepoint=>true, :read_only=>true){@db[:public__testfk].insert}}}.should raise_error(Sequel::DatabaseError)
184
197
  proc{@db.transaction(:auto_savepoint=>true, :read_only=>true){@db.transaction(:read_only=>false){@db[:public__testfk].insert}}}.should raise_error(Sequel::DatabaseError)
@@ -257,6 +270,12 @@ describe "A PostgreSQL database" do
257
270
  ds.insert(:id=>1)
258
271
  ds.select_map(:id).should == [1]
259
272
  end
273
+
274
+ specify "should have notice receiver receive notices" do
275
+ a = nil
276
+ Sequel.connect(DB.opts.merge(:notice_receiver=>proc{|r| a = r.result_error_message})){|db| db.do("BEGIN\nRAISE WARNING 'foo';\nEND;")}
277
+ a.should == "WARNING: foo\n"
278
+ end if DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && DB.server_version >= 90000
260
279
  end
261
280
 
262
281
  describe "A PostgreSQL database with domain types" do
@@ -1035,6 +1054,18 @@ describe "Postgres::Dataset#insert" do
1035
1054
  @ds.first(:xid=>h[:xid])[:value].should == 10
1036
1055
  end
1037
1056
 
1057
+ specify "should have insert_select respect existing returning clause" do
1058
+ h = @ds.returning(:value___v, :xid___x).insert_select(:value=>10)
1059
+ h[:v].should == 10
1060
+ @ds.first(:xid=>h[:x])[:value].should == 10
1061
+ end
1062
+
1063
+ specify "should have prepared insert_select respect existing returning clause" do
1064
+ h = @ds.returning(:value___v, :xid___x).prepare(:insert_select, :insert_select, :value=>10).call
1065
+ h[:v].should == 10
1066
+ @ds.first(:xid=>h[:x])[:value].should == 10
1067
+ end
1068
+
1038
1069
  specify "should correctly return the inserted record's primary key value" do
1039
1070
  value1 = 10
1040
1071
  id1 = @ds.insert(:value=>value1)
@@ -2466,14 +2497,11 @@ describe 'PostgreSQL hstore handling' do
2466
2497
  @ds.get(h2.merge(h3).keys.length).should == 2
2467
2498
  @ds.get(h1.merge(h3).keys.length).should == 3
2468
2499
 
2469
- unless [:do].include?(@db.adapter_scheme)
2470
- # Broken DataObjects thinks operators with ? represent placeholders
2471
- @ds.get(h1.contain_all(%w'a c')).should == true
2472
- @ds.get(h1.contain_all(%w'a d')).should == false
2500
+ @ds.get(h1.contain_all(%w'a c')).should == true
2501
+ @ds.get(h1.contain_all(%w'a d')).should == false
2473
2502
 
2474
- @ds.get(h1.contain_any(%w'a d')).should == true
2475
- @ds.get(h1.contain_any(%w'e d')).should == false
2476
- end
2503
+ @ds.get(h1.contain_any(%w'a d')).should == true
2504
+ @ds.get(h1.contain_any(%w'e d')).should == false
2477
2505
 
2478
2506
  @ds.get(h1.contains(h2)).should == true
2479
2507
  @ds.get(h1.contains(h3)).should == false
@@ -2491,18 +2519,16 @@ describe 'PostgreSQL hstore handling' do
2491
2519
 
2492
2520
  @ds.from(Sequel.hstore('a'=>'b', 'c'=>nil).op.each).order(:key).all.should == [{:key=>'a', :value=>'b'}, {:key=>'c', :value=>nil}]
2493
2521
 
2494
- unless [:do].include?(@db.adapter_scheme)
2495
- @ds.get(h1.has_key?('c')).should == true
2496
- @ds.get(h1.include?('c')).should == true
2497
- @ds.get(h1.key?('c')).should == true
2498
- @ds.get(h1.member?('c')).should == true
2499
- @ds.get(h1.exist?('c')).should == true
2500
- @ds.get(h1.has_key?('d')).should == false
2501
- @ds.get(h1.include?('d')).should == false
2502
- @ds.get(h1.key?('d')).should == false
2503
- @ds.get(h1.member?('d')).should == false
2504
- @ds.get(h1.exist?('d')).should == false
2505
- end
2522
+ @ds.get(h1.has_key?('c')).should == true
2523
+ @ds.get(h1.include?('c')).should == true
2524
+ @ds.get(h1.key?('c')).should == true
2525
+ @ds.get(h1.member?('c')).should == true
2526
+ @ds.get(h1.exist?('c')).should == true
2527
+ @ds.get(h1.has_key?('d')).should == false
2528
+ @ds.get(h1.include?('d')).should == false
2529
+ @ds.get(h1.key?('d')).should == false
2530
+ @ds.get(h1.member?('d')).should == false
2531
+ @ds.get(h1.exist?('d')).should == false
2506
2532
 
2507
2533
  @ds.get(h1.hstore.hstore.hstore.keys.length).should == 2
2508
2534
  @ds.get(h1.keys.length).should == 2
@@ -3022,6 +3048,7 @@ describe 'PostgreSQL interval types' do
3022
3048
  ['1 second', '00:00:01', 1, [[:seconds, 1]]],
3023
3049
  ['1 minute', '00:01:00', 60, [[:seconds, 60]]],
3024
3050
  ['1 hour', '01:00:00', 3600, [[:seconds, 3600]]],
3051
+ ['123000 hours', '123000:00:00', 442800000, [[:seconds, 442800000]]],
3025
3052
  ['1 day', '1 day', 86400, [[:days, 1]]],
3026
3053
  ['1 week', '7 days', 86400*7, [[:days, 7]]],
3027
3054
  ['1 month', '1 mon', 86400*30, [[:months, 1]]],
@@ -3390,7 +3417,8 @@ describe 'pg_static_cache_updater extension' do
3390
3417
  q = Queue.new
3391
3418
  q1 = Queue.new
3392
3419
 
3393
- @db.listen_for_static_cache_updates(@Thing, :timeout=>0, :loop=>proc{q.push(nil); q1.pop.call})
3420
+ @db.listen_for_static_cache_updates(@Thing, :timeout=>0, :loop=>proc{q.push(nil); q1.pop.call}, :before_thread_exit=>proc{q.push(nil)})
3421
+
3394
3422
  q.pop
3395
3423
  q1.push(proc{@db[:things].insert(1, 'A')})
3396
3424
  q.pop
@@ -3405,5 +3433,6 @@ describe 'pg_static_cache_updater extension' do
3405
3433
  @Thing.all.should == []
3406
3434
 
3407
3435
  q1.push(proc{throw :stop})
3436
+ q.pop
3408
3437
  end
3409
3438
  end if DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && DB.server_version >= 90000
data/spec/bin_spec.rb CHANGED
@@ -88,8 +88,8 @@ END
88
88
  DB2.tables.sort_by{|t| t.to_s}.should == [:a, :b]
89
89
  DB[:a].all.should == [{:a=>1, :name=>'foo'}]
90
90
  DB[:b].all.should == [{:a=>1}]
91
- DB2.schema(:a).should == [[:a, {:allow_null=>false, :default=>nil, :primary_key=>true, :db_type=>"integer", :type=>:integer, :ruby_default=>nil}], [:name, {:allow_null=>true, :default=>nil, :primary_key=>false, :db_type=>"varchar(255)", :type=>:string, :ruby_default=>nil}]]
92
- DB2.schema(:b).should == [[:a, {:allow_null=>true, :default=>nil, :primary_key=>false, :db_type=>"integer", :type=>:integer, :ruby_default=>nil}]]
91
+ DB2.schema(:a).map{|col, sch| [col, *sch.values_at(:allow_null, :default, :primary_key, :db_type, :type, :ruby_default)]}.should == [[:a, false, nil, true, "integer", :integer, nil], [:name, true, nil, false, "varchar(255)", :string, nil]]
92
+ DB2.schema(:b).map{|col, sch| [col, *sch.values_at(:allow_null, :default, :primary_key, :db_type, :type, :ruby_default)]}.should == [[:a, true, nil, false, "integer", :integer, nil]]
93
93
  DB2.indexes(:a).should == {}
94
94
  DB2.indexes(:b).should == {:b_a_index=>{:unique=>false, :columns=>[:a]}}
95
95
  DB2.foreign_key_list(:a).should == []
@@ -2127,12 +2127,20 @@ describe "Dataset#join_table" do
2127
2127
  @d.cross_join(:categories).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
2128
2128
  end
2129
2129
 
2130
- specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
2131
- proc{@d.natural_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
2132
- proc{@d.natural_left_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
2133
- proc{@d.natural_right_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
2134
- proc{@d.natural_full_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
2135
- proc{@d.cross_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
2130
+ specify "should support options hashes for join methods that don't take conditions" do
2131
+ @d.natural_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories" AS "a"'
2132
+ @d.natural_left_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL LEFT JOIN "categories" AS "a"'
2133
+ @d.natural_right_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories" AS "a"'
2134
+ @d.natural_full_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL FULL JOIN "categories" AS "a"'
2135
+ @d.cross_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories" AS "a"'
2136
+ end
2137
+
2138
+ specify "should raise an error if non-hash arguments are provided to join methods that don't take conditions" do
2139
+ proc{@d.natural_join(:categories, nil)}.should raise_error(Sequel::Error)
2140
+ proc{@d.natural_left_join(:categories, nil)}.should raise_error(Sequel::Error)
2141
+ proc{@d.natural_right_join(:categories, nil)}.should raise_error(Sequel::Error)
2142
+ proc{@d.natural_full_join(:categories, nil)}.should raise_error(Sequel::Error)
2143
+ proc{@d.cross_join(:categories, nil)}.should raise_error(Sequel::Error)
2136
2144
  end
2137
2145
 
2138
2146
  specify "should raise an error if blocks are provided to join methods that don't pass them" do
@@ -2184,6 +2192,10 @@ describe "Dataset#join_table" do
2184
2192
  @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
2185
2193
  end
2186
2194
 
2195
+ specify "should support the :reset_implicit_qualifier option" do
2196
+ @d.from(:stats).join(:a, [:b], :reset_implicit_qualifier=>false).join(:players, {:id => :player_id}).sql.should == 'SELECT * FROM "stats" INNER JOIN "a" USING ("b") INNER JOIN "players" ON ("players"."id" = "stats"."player_id")'
2197
+ end
2198
+
2187
2199
  specify "should default :qualify option to default_join_table_qualification" do
2188
2200
  def @d.default_join_table_qualification; false; end
2189
2201
  @d.from('stats').join(:players, :id => :player_id).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("id" = "player_id")'
@@ -1550,7 +1550,7 @@ describe "Schema Parser" do
1550
1550
  specify "should correctly parse all supported data types" do
1551
1551
  sm = Module.new do
1552
1552
  def schema_parse_table(t, opts)
1553
- [[:x, {:type=>schema_column_type(t.to_s)}]]
1553
+ [[:x, {:db_type=>t.to_s, :type=>schema_column_type(t.to_s)}]]
1554
1554
  end
1555
1555
  end
1556
1556
  @db.extend(sm)
@@ -1563,6 +1563,7 @@ describe "Schema Parser" do
1563
1563
  @db.schema(:"character varying").first.last[:type].should == :string
1564
1564
  @db.schema(:varchar).first.last[:type].should == :string
1565
1565
  @db.schema(:"varchar(255)").first.last[:type].should == :string
1566
+ @db.schema(:"varchar(255)").first.last[:max_length].should == 255
1566
1567
  @db.schema(:text).first.last[:type].should == :string
1567
1568
  @db.schema(:date).first.last[:type].should == :date
1568
1569
  @db.schema(:datetime).first.last[:type].should == :datetime
@@ -2,13 +2,13 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Sequel::Plugins::AutoValidations" do
4
4
  before do
5
- db = Sequel.mock(:fetch=>{:v=>1})
5
+ db = Sequel.mock(:fetch=>proc{|sql| sql =~ /a{51}/ ? {:v=>0} : {:v=>1}})
6
6
  def db.schema_parse_table(*) true; end
7
7
  def db.schema(t, *)
8
8
  t = t.first_source if t.is_a?(Sequel::Dataset)
9
9
  return [] if t != :test
10
10
  [[:id, {:primary_key=>true, :type=>:integer, :allow_null=>false}],
11
- [:name, {:primary_key=>false, :type=>:string, :allow_null=>false}],
11
+ [:name, {:primary_key=>false, :type=>:string, :allow_null=>false, :max_length=>50}],
12
12
  [:num, {:primary_key=>false, :type=>:integer, :allow_null=>true}],
13
13
  [:d, {:primary_key=>false, :type=>:date, :allow_null=>false}],
14
14
  [:nnd, {:primary_key=>false, :type=>:string, :allow_null=>false, :ruby_default=>'nnd'}]]
@@ -42,6 +42,10 @@ describe "Sequel::Plugins::AutoValidations" do
42
42
  @m.set(:d=>Date.today, :num=>1)
43
43
  @m.valid?.should == false
44
44
  @m.errors.should == {[:name, :num]=>["is already taken"]}
45
+
46
+ @m.set(:name=>'a'*51)
47
+ @m.valid?.should == false
48
+ @m.errors.should == {:name=>["is longer than 50 characters"]}
45
49
  end
46
50
 
47
51
  it "should handle databases that don't support index parsing" do
@@ -86,6 +90,13 @@ describe "Sequel::Plugins::AutoValidations" do
86
90
 
87
91
  @c.skip_auto_validations(:unique)
88
92
  @m.valid?.should == true
93
+
94
+ @m.set(:name=>'a'*51)
95
+ @m.valid?.should == false
96
+ @m.errors.should == {:name=>["is longer than 50 characters"]}
97
+
98
+ @c.skip_auto_validations(:max_length)
99
+ @m.valid?.should == true
89
100
  end
90
101
 
91
102
  it "should allow skipping all auto validations" do
@@ -95,6 +106,8 @@ describe "Sequel::Plugins::AutoValidations" do
95
106
  @m.valid?.should == true
96
107
  @m.set(:d=>'/', :num=>'a', :name=>'1')
97
108
  @m.valid?.should == true
109
+ @m.set(:name=>'a'*51)
110
+ @m.valid?.should == true
98
111
  end
99
112
 
100
113
  it "should work correctly in subclasses" do
@@ -110,6 +123,10 @@ describe "Sequel::Plugins::AutoValidations" do
110
123
  @m.set(:d=>Date.today, :num=>1)
111
124
  @m.valid?.should == false
112
125
  @m.errors.should == {[:name, :num]=>["is already taken"]}
126
+
127
+ @m.set(:name=>'a'*51)
128
+ @m.valid?.should == false
129
+ @m.errors.should == {:name=>["is longer than 50 characters"]}
113
130
  end
114
131
 
115
132
  it "should work correctly in STI subclasses" do
@@ -128,6 +145,10 @@ describe "Sequel::Plugins::AutoValidations" do
128
145
  @m.valid?.should == false
129
146
  @m.errors.should == {[:name, :num]=>["is already taken"]}
130
147
  @m.db.sqls.should == ["SELECT count(*) AS count FROM test WHERE ((name = '1') AND (num = 1)) LIMIT 1"]
148
+
149
+ @m.set(:name=>'a'*51)
150
+ @m.valid?.should == false
151
+ @m.errors.should == {:name=>["is longer than 50 characters"]}
131
152
  end
132
153
 
133
154
  it "should work correctly when changing the dataset" do
@@ -38,6 +38,7 @@ describe "NestedAttributes plugin" do
38
38
  @Artist.one_to_one :first_album, :class=>@Album, :key=>:artist_id
39
39
  @Album.many_to_one :artist, :class=>@Artist, :reciprocal=>:albums
40
40
  @Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
41
+ @Tag.many_to_many :albums, :class=>@Album, :left_key=>:tag_id, :right_key=>:album_id, :join_table=>:at
41
42
  @Artist.nested_attributes :albums, :first_album, :destroy=>true, :remove=>true
42
43
  @Artist.nested_attributes :concerts, :destroy=>true, :remove=>true
43
44
  @Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
@@ -117,9 +118,12 @@ describe "NestedAttributes plugin" do
117
118
  end
118
119
 
119
120
  it "should add new objects to the cached association array as soon as the *_attributes= method is called" do
120
- a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al', :tags_attributes=>[{:name=>'T'}]}]})
121
+ a = @Artist.new({:name=>'Ar', :first_album_attributes=>{:name=>'B'}, :albums_attributes=>[{:name=>'Al', :tags_attributes=>[{:name=>'T'}]}]})
121
122
  a.albums.should == [@Album.new(:name=>'Al')]
123
+ a.albums.first.artist.should == a
122
124
  a.albums.first.tags.should == [@Tag.new(:name=>'T')]
125
+ a.first_album.should == @Album.new(:name=>'B')
126
+ a.first_album.artist.should == a
123
127
  end
124
128
 
125
129
  it "should support creating new objects with composite primary keys" do
@@ -608,4 +612,31 @@ describe "NestedAttributes plugin" do
608
612
  proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
609
613
  proc{al.set(:tags_attributes=>[{:name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
610
614
  end
615
+
616
+ it "should allow per-call options via the set_nested_attributes method" do
617
+ @Tag.columns :id, :name, :number
618
+ @Album.nested_attributes :tags
619
+
620
+ al = @Album.load(:id=>10, :name=>'Al')
621
+ t = @Tag.load(:id=>30, :name=>'T', :number=>10)
622
+ al.associations[:tags] = [t]
623
+ al.set_nested_attributes(:tags, [{:id=>30, :name=>'T2'}, {:name=>'T3'}], :fields=>[:name])
624
+ @db.sqls.should == []
625
+ al.save
626
+ check_sql_array("UPDATE albums SET name = 'Al' WHERE (id = 10)",
627
+ "UPDATE tags SET name = 'T2' WHERE (id = 30)",
628
+ "INSERT INTO tags (name) VALUES ('T3')",
629
+ ["INSERT INTO at (album_id, tag_id) VALUES (10, 1)", "INSERT INTO at (tag_id, album_id) VALUES (1, 10)"])
630
+ proc{al.set_nested_attributes(:tags, [{:id=>30, :name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
631
+ proc{al.set_nested_attributes(:tags, [{:name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
632
+ end
633
+
634
+ it "should have set_nested_attributes method raise error if called with a bad association" do
635
+ proc{@Album.load(:id=>10, :name=>'Al').set_nested_attributes(:tags2, [{:id=>30, :name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
636
+ end
637
+
638
+ it "should have set_nested_attributes method raise error if called with an association that doesn't support nested attributes" do
639
+ @Tag.columns :id, :name, :number
640
+ proc{@Album.load(:id=>10, :name=>'Al').set_nested_attributes(:tags, [{:id=>30, :name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
641
+ end
611
642
  end
@@ -77,4 +77,16 @@ describe "pg_static_cache_updater extension" do
77
77
  @db.fetch = {:v=>1234}
78
78
  proc{@db.listen_for_static_cache_updates(Class.new(Sequel::Model(@db[:table])))}.should raise_error(Sequel::Error)
79
79
  end
80
+
81
+ specify "#listen_for_static_cache_updates should handle a :before_thread_exit option" do
82
+ a = []
83
+ @db.listen_for_static_cache_updates([@model], :yield=>[nil, nil, 12345], :before_thread_exit=>proc{a << 1}).join
84
+ a.should == [1]
85
+ end
86
+
87
+ specify "#listen_for_static_cache_updates should call :before_thread_exit option even if listen raises an exception" do
88
+ a = []
89
+ @db.listen_for_static_cache_updates([@model], :yield=>[nil, nil, 12345], :after_listen=>proc{raise ArgumentError}, :before_thread_exit=>proc{a << 1}).join
90
+ a.should == [1]
91
+ end
80
92
  end
@@ -31,25 +31,25 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
31
31
 
32
32
  specify "should run correct SQL for associations" do
33
33
  @Artist.load(:id=>1).albums
34
- @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1) -- prepared"]
34
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE (albums.artist_id = 1) -- prepared"]
35
35
 
36
36
  @Artist.load(:id=>1).album
37
- @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1) LIMIT 1 -- prepared"]
37
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE (albums.artist_id = 1) LIMIT 1 -- prepared"]
38
38
 
39
39
  @Album.load(:id=>1, :artist_id=>2).artist
40
- @db.sqls.should == ["SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1 -- prepared"]
40
+ @db.sqls.should == ["SELECT id, id2 FROM artists WHERE (artists.id = 2) LIMIT 1 -- prepared"]
41
41
 
42
42
  @Album.load(:id=>1, :artist_id=>2).tags
43
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (albums_tags.album_id = 1) -- prepared"]
43
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (albums_tags.album_id = 1) -- prepared"]
44
44
 
45
45
  @Album.load(:id=>1, :artist_id=>2).tag
46
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (albums_tags.album_id = 1) LIMIT 1 -- prepared"]
46
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (albums_tags.album_id = 1) LIMIT 1 -- prepared"]
47
47
 
48
48
  @Artist.load(:id=>1).tags
49
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (albums.artist_id = 1) -- prepared"]
49
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (albums.artist_id = 1) -- prepared"]
50
50
 
51
51
  @Artist.load(:id=>1).tag
52
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (albums.artist_id = 1) LIMIT 1 -- prepared"]
52
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (albums.artist_id = 1) LIMIT 1 -- prepared"]
53
53
  end
54
54
 
55
55
  specify "should run correct SQL for composite key associations" do
@@ -63,25 +63,25 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
63
63
  @Artist.one_through_many :tag, :clone=>:tags
64
64
 
65
65
  @Artist.load(:id=>1, :id2=>2).albums
66
- @db.sqls.should == ["SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
66
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
67
67
 
68
68
  @Artist.load(:id=>1, :id2=>2).album
69
- @db.sqls.should == ["SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) LIMIT 1 -- prepared"]
69
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) LIMIT 1 -- prepared"]
70
70
 
71
71
  @Album.load(:id=>1, :artist_id=>2, :artist_id2=>3).artist
72
- @db.sqls.should == ["SELECT * FROM artists WHERE ((artists.id = 2) AND (artists.id2 = 3)) LIMIT 1 -- prepared"]
72
+ @db.sqls.should == ["SELECT id, id2 FROM artists WHERE ((artists.id = 2) AND (artists.id2 = 3)) LIMIT 1 -- prepared"]
73
73
 
74
74
  @Album.load(:id=>1, :artist_id=>2, :id2=>3).tags
75
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) WHERE ((albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) -- prepared"]
75
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) WHERE ((albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) -- prepared"]
76
76
 
77
77
  @Album.load(:id=>1, :artist_id=>2, :id2=>3).tag
78
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) WHERE ((albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) LIMIT 1 -- prepared"]
78
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) WHERE ((albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) LIMIT 1 -- prepared"]
79
79
 
80
80
  @Artist.load(:id=>1, :id2=>2).tags
81
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2)) WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
81
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2)) WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
82
82
 
83
83
  @Artist.load(:id=>1, :id2=>2).tag
84
- @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2)) WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) LIMIT 1 -- prepared"]
84
+ @db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2)) WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) LIMIT 1 -- prepared"]
85
85
  end
86
86
 
87
87
  specify "should not run query if no objects can be associated" do
@@ -120,19 +120,19 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
120
120
  @Album.dataset = @Album.dataset.where(:a=>2)
121
121
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
122
122
  @Artist.load(:id=>1).albums
123
- @db.sqls.should == ["SELECT * FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
123
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
124
124
  end
125
125
 
126
126
  specify "should use a prepared statement if the :conditions association option" do
127
127
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>2}
128
128
  @Artist.load(:id=>1).albums
129
- @db.sqls.should == ["SELECT * FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
129
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
130
130
  end
131
131
 
132
132
  specify "should not use a prepared statement if :conditions association option uses an identifier" do
133
133
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{Sequel.identifier('a')=>2}
134
134
  @Artist.load(:id=>1).albums
135
- @db.sqls.should == ["SELECT * FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
135
+ @db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
136
136
  end
137
137
 
138
138
  specify "should run a regular query if :dataset option is used when defining the association" do