sequel 3.20.0 → 3.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG +32 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +1 -1
  4. data/Rakefile +13 -3
  5. data/bin/sequel +18 -5
  6. data/doc/active_record.rdoc +4 -4
  7. data/doc/opening_databases.rdoc +38 -1
  8. data/doc/release_notes/3.21.0.txt +87 -0
  9. data/doc/validations.rdoc +2 -2
  10. data/lib/sequel/adapters/informix.rb +1 -1
  11. data/lib/sequel/adapters/jdbc/h2.rb +2 -5
  12. data/lib/sequel/adapters/jdbc/mssql.rb +1 -4
  13. data/lib/sequel/adapters/jdbc/mysql.rb +1 -4
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -6
  15. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -8
  16. data/lib/sequel/adapters/shared/mssql.rb +8 -0
  17. data/lib/sequel/adapters/shared/mysql.rb +23 -3
  18. data/lib/sequel/adapters/shared/oracle.rb +2 -2
  19. data/lib/sequel/adapters/tinytds.rb +125 -0
  20. data/lib/sequel/database/connecting.rb +1 -1
  21. data/lib/sequel/database/schema_methods.rb +37 -5
  22. data/lib/sequel/dataset/sql.rb +6 -6
  23. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  24. data/lib/sequel/model/base.rb +50 -0
  25. data/lib/sequel/model/plugins.rb +0 -55
  26. data/lib/sequel/plugins/association_autoreloading.rb +48 -0
  27. data/lib/sequel/plugins/validation_class_methods.rb +6 -5
  28. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  29. data/lib/sequel/version.rb +1 -1
  30. data/spec/adapters/firebird_spec.rb +6 -6
  31. data/spec/adapters/informix_spec.rb +2 -2
  32. data/spec/adapters/mssql_spec.rb +18 -13
  33. data/spec/adapters/mysql_spec.rb +47 -20
  34. data/spec/adapters/oracle_spec.rb +40 -4
  35. data/spec/adapters/postgres_spec.rb +14 -14
  36. data/spec/adapters/spec_helper.rb +1 -1
  37. data/spec/adapters/sqlite_spec.rb +8 -8
  38. data/spec/core/connection_pool_spec.rb +18 -17
  39. data/spec/core/core_sql_spec.rb +18 -18
  40. data/spec/core/database_spec.rb +62 -62
  41. data/spec/core/dataset_spec.rb +105 -92
  42. data/spec/core/expression_filters_spec.rb +2 -2
  43. data/spec/core/schema_spec.rb +6 -6
  44. data/spec/core/version_spec.rb +1 -1
  45. data/spec/extensions/association_autoreloading_spec.rb +94 -0
  46. data/spec/extensions/blank_spec.rb +6 -6
  47. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  48. data/spec/extensions/migration_spec.rb +6 -6
  49. data/spec/extensions/pagination_spec.rb +2 -2
  50. data/spec/extensions/pretty_table_spec.rb +2 -2
  51. data/spec/extensions/query_spec.rb +2 -2
  52. data/spec/extensions/schema_dumper_spec.rb +2 -1
  53. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  54. data/spec/extensions/sql_expr_spec.rb +1 -1
  55. data/spec/extensions/string_date_time_spec.rb +4 -4
  56. data/spec/extensions/validation_class_methods_spec.rb +2 -2
  57. data/spec/integration/dataset_test.rb +8 -3
  58. data/spec/integration/plugin_test.rb +5 -5
  59. data/spec/integration/prepared_statement_test.rb +1 -1
  60. data/spec/integration/schema_test.rb +7 -0
  61. data/spec/integration/spec_helper.rb +14 -1
  62. data/spec/integration/timezone_test.rb +4 -4
  63. data/spec/integration/type_test.rb +1 -1
  64. data/spec/model/model_spec.rb +3 -3
  65. metadata +9 -4
@@ -72,7 +72,7 @@ module Sequel
72
72
 
73
73
  # Check attribute value(s) is included in the given set.
74
74
  def validates_includes(set, atts, opts={})
75
- validatable_attributes_for_type(:includes, atts, opts){|a,v,m| validation_error_message(m, set) unless set.include?(v)}
75
+ validatable_attributes_for_type(:includes, atts, opts){|a,v,m| validation_error_message(m, set) unless set.send(set.respond_to?(:cover?) ? :cover? : :include?, v)}
76
76
  end
77
77
 
78
78
  # Check attribute value(s) string representation is a valid integer.
@@ -89,7 +89,7 @@ module Sequel
89
89
 
90
90
  # Check that the attribute values length is in the specified range.
91
91
  def validates_length_range(range, atts, opts={})
92
- validatable_attributes_for_type(:length_range, atts, opts){|a,v,m| validation_error_message(m, range) unless v && range.include?(v.length)}
92
+ validatable_attributes_for_type(:length_range, atts, opts){|a,v,m| validation_error_message(m, range) unless v && range.send(range.respond_to?(:cover?) ? :cover? : :include?, v.length)}
93
93
  end
94
94
 
95
95
  # Check that the attribute values are not longer than the given max length.
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 20
6
+ MINOR = 21
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
@@ -43,7 +43,7 @@ FIREBIRD_DB.create_table! :test6 do
43
43
  String :val4, :text=>true
44
44
  end
45
45
 
46
- context "A Firebird database" do
46
+ describe "A Firebird database" do
47
47
  before do
48
48
  @db = FIREBIRD_DB
49
49
  end
@@ -60,7 +60,7 @@ context "A Firebird database" do
60
60
  end
61
61
  end
62
62
 
63
- context "A Firebird dataset" do
63
+ describe "A Firebird dataset" do
64
64
  before do
65
65
  @d = FIREBIRD_DB[:test]
66
66
  @d.delete # remove all records
@@ -230,7 +230,7 @@ context "A Firebird dataset" do
230
230
  end
231
231
  end
232
232
 
233
- context "A Firebird dataset with a timestamp field" do
233
+ describe "A Firebird dataset with a timestamp field" do
234
234
  before do
235
235
  @d = FIREBIRD_DB[:test3]
236
236
  @d.delete
@@ -244,7 +244,7 @@ context "A Firebird dataset with a timestamp field" do
244
244
  end
245
245
  end
246
246
 
247
- context "A Firebird database" do
247
+ describe "A Firebird database" do
248
248
  before do
249
249
  @db = FIREBIRD_DB
250
250
  @db.drop_table(:posts) rescue nil
@@ -353,7 +353,7 @@ context "A Firebird database" do
353
353
  end
354
354
  end
355
355
 
356
- context "Postgres::Dataset#insert" do
356
+ describe "Postgres::Dataset#insert" do
357
357
  before do
358
358
  @ds = FIREBIRD_DB[:test5]
359
359
  @ds.delete
@@ -387,7 +387,7 @@ context "Postgres::Dataset#insert" do
387
387
  end
388
388
  end
389
389
 
390
- context "Postgres::Dataset#insert" do
390
+ describe "Postgres::Dataset#insert" do
391
391
  before do
392
392
  @ds = FIREBIRD_DB[:test6]
393
393
  @ds.delete
@@ -15,7 +15,7 @@ INFORMIX_DB.create_table :test do
15
15
  index :value
16
16
  end
17
17
 
18
- context "A Informix database" do
18
+ describe "A Informix database" do
19
19
  specify "should provide disconnect functionality" do
20
20
  INFORMIX_DB.execute("select user from dual")
21
21
  INFORMIX_DB.pool.size.should == 1
@@ -24,7 +24,7 @@ context "A Informix database" do
24
24
  end
25
25
  end
26
26
 
27
- context "A Informix dataset" do
27
+ describe "A Informix dataset" do
28
28
  before do
29
29
  @d = INFORMIX_DB[:test]
30
30
  @d.delete # remove all records
@@ -34,7 +34,7 @@ MSSQL_DB.create_table! :test4 do
34
34
  varbinary :value
35
35
  end
36
36
 
37
- context "A MSSQL database" do
37
+ describe "A MSSQL database" do
38
38
  before do
39
39
  @db = MSSQL_DB
40
40
  end
@@ -59,7 +59,7 @@ context "A MSSQL database" do
59
59
  end
60
60
  end
61
61
 
62
- context "MSSQL Dataset#join_table" do
62
+ describe "MSSQL Dataset#join_table" do
63
63
  specify "should emulate the USING clause with ON" do
64
64
  MSSQL_DB[:items].join(:categories, [:id]).sql.should ==
65
65
  'SELECT * FROM ITEMS INNER JOIN CATEGORIES ON (CATEGORIES.ID = ITEMS.ID)'
@@ -71,7 +71,7 @@ context "MSSQL Dataset#join_table" do
71
71
  end
72
72
  end
73
73
 
74
- context "MSSQL Dataset#output" do
74
+ describe "MSSQL Dataset#output" do
75
75
  before do
76
76
  @db = MSSQL_DB
77
77
  @db.create_table!(:items){String :name; Integer :value}
@@ -150,13 +150,13 @@ context "MSSQL Dataset#output" do
150
150
  end
151
151
  end
152
152
 
153
- context "MSSQL dataset" do
153
+ describe "MSSQL dataset" do
154
154
  before do
155
155
  @db = MSSQL_DB
156
156
  @ds = MSSQL_DB[:t]
157
157
  end
158
158
 
159
- context "using #with and #with_recursive" do
159
+ describe "using #with and #with_recursive" do
160
160
  before do
161
161
  @ds1 = @ds.with(:t, @db[:x])
162
162
  @ds2 = @ds.with_recursive(:t, @db[:x], @db[:t])
@@ -177,7 +177,12 @@ context "MSSQL dataset" do
177
177
  @ds2.insert_sql(@db[:t]).should == 'WITH T AS (SELECT * FROM X UNION ALL SELECT * FROM T) INSERT INTO T SELECT * FROM T'
178
178
  end
179
179
 
180
- context "on #import" do
180
+ specify "should move WITH clause on joined dataset to top level" do
181
+ @db[:s].inner_join(@ds1).sql.should == "WITH T AS (SELECT * FROM X) SELECT * FROM S INNER JOIN (SELECT * FROM T) AS T1"
182
+ @ds1.inner_join(@db[:s].with(:s, @db[:y])).sql.should == "WITH T AS (SELECT * FROM X), S AS (SELECT * FROM Y) SELECT * FROM T INNER JOIN (SELECT * FROM S) AS T1"
183
+ end
184
+
185
+ describe "on #import" do
181
186
  before do
182
187
  @db = @db.clone
183
188
  class << @db
@@ -213,7 +218,7 @@ context "MSSQL dataset" do
213
218
  end
214
219
  end
215
220
 
216
- context "MSSQL joined datasets" do
221
+ describe "MSSQL joined datasets" do
217
222
  before do
218
223
  @db = MSSQL_DB
219
224
  end
@@ -325,7 +330,7 @@ describe "Common Table Expressions" do
325
330
  end
326
331
  end
327
332
 
328
- context "MSSSQL::Dataset#insert" do
333
+ describe "MSSSQL::Dataset#insert" do
329
334
  before do
330
335
  @db = MSSQL_DB
331
336
  @db.create_table!(:test5){primary_key :xid; Integer :value}
@@ -352,13 +357,13 @@ context "MSSSQL::Dataset#insert" do
352
357
  end
353
358
  end
354
359
 
355
- context "MSSSQL::Dataset#disable_insert_output" do
360
+ describe "MSSSQL::Dataset#disable_insert_output" do
356
361
  specify "should play nicely with simple_select_all?" do
357
362
  MSSQL_DB[:test].disable_insert_output.send(:simple_select_all?).should == true
358
363
  end
359
364
  end
360
365
 
361
- context "MSSSQL::Dataset#into" do
366
+ describe "MSSSQL::Dataset#into" do
362
367
  before do
363
368
  @db = MSSQL_DB
364
369
  end
@@ -377,7 +382,7 @@ context "MSSSQL::Dataset#into" do
377
382
  end
378
383
  end
379
384
 
380
- context "A MSSQL database" do
385
+ describe "A MSSQL database" do
381
386
  before do
382
387
  @db = MSSQL_DB
383
388
  end
@@ -403,7 +408,7 @@ context "A MSSQL database" do
403
408
  end
404
409
  end
405
410
 
406
- context "MSSQL::Database#rename_table" do
411
+ describe "MSSQL::Database#rename_table" do
407
412
  specify "should work on non-schema bound tables which need escaping" do
408
413
  MSSQL_DB.quote_identifiers = true
409
414
  MSSQL_DB.create_table! :'foo bar' do
@@ -426,7 +431,7 @@ context "MSSQL::Database#rename_table" do
426
431
  end
427
432
  end
428
433
 
429
- context "MSSQL::Dataset#count" do
434
+ describe "MSSQL::Dataset#count" do
430
435
  specify "should work with a distinct query with an order clause" do
431
436
  MSSQL_DB.create_table!(:items){String :name; Integer :value}
432
437
  MSSQL_DB[:items].insert(:name => "name", :value => 1)
@@ -34,7 +34,7 @@ SQL_BEGIN = 'BEGIN'
34
34
  SQL_ROLLBACK = 'ROLLBACK'
35
35
  SQL_COMMIT = 'COMMIT'
36
36
 
37
- context "MySQL", '#create_table' do
37
+ describe "MySQL", '#create_table' do
38
38
  before do
39
39
  @db = MYSQL_DB
40
40
  MYSQL_DB.sqls.clear
@@ -84,7 +84,7 @@ context "MySQL", '#create_table' do
84
84
  end
85
85
  end
86
86
 
87
- context "A MySQL database" do
87
+ describe "A MySQL database" do
88
88
  specify "should provide the server version" do
89
89
  MYSQL_DB.server_version.should >= 40000
90
90
  end
@@ -99,7 +99,7 @@ context "A MySQL database" do
99
99
  end
100
100
 
101
101
  if MYSQL_DB.adapter_scheme == :mysql
102
- context "Sequel::MySQL.convert_tinyint_to_bool" do
102
+ describe "Sequel::MySQL.convert_tinyint_to_bool" do
103
103
  before do
104
104
  @db = MYSQL_DB
105
105
  @db.create_table(:booltest){column :b, 'tinyint(1)'; column :i, 'tinyint(4)'}
@@ -148,7 +148,7 @@ if MYSQL_DB.adapter_scheme == :mysql
148
148
  end
149
149
  end
150
150
 
151
- context "A MySQL dataset" do
151
+ describe "A MySQL dataset" do
152
152
  before do
153
153
  MYSQL_DB.create_table(:items){String :name; Integer :value}
154
154
  @d = MYSQL_DB[:items]
@@ -243,7 +243,7 @@ context "A MySQL dataset" do
243
243
  end
244
244
  end
245
245
 
246
- context "MySQL datasets" do
246
+ describe "MySQL datasets" do
247
247
  before do
248
248
  @d = MYSQL_DB[:orders]
249
249
  end
@@ -282,7 +282,7 @@ describe "Dataset#distinct" do
282
282
  end
283
283
  end
284
284
 
285
- context "MySQL join expressions" do
285
+ describe "MySQL join expressions" do
286
286
  before do
287
287
  @ds = MYSQL_DB[:nodes]
288
288
  @ds.db.meta_def(:server_version) {50014}
@@ -333,7 +333,7 @@ context "MySQL join expressions" do
333
333
  end
334
334
  end
335
335
 
336
- context "Joined MySQL dataset" do
336
+ describe "Joined MySQL dataset" do
337
337
  before do
338
338
  @ds = MYSQL_DB[:nodes]
339
339
  end
@@ -357,7 +357,7 @@ context "Joined MySQL dataset" do
357
357
  end
358
358
  end
359
359
 
360
- context "A MySQL database" do
360
+ describe "A MySQL database" do
361
361
  before do
362
362
  @db = MYSQL_DB
363
363
  end
@@ -424,7 +424,7 @@ context "A MySQL database" do
424
424
  end
425
425
  end
426
426
 
427
- context "A MySQL database with table options" do
427
+ describe "A MySQL database with table options" do
428
428
  before do
429
429
  @options = {:engine=>'MyISAM', :charset=>'latin1', :collate => 'latin1_swedish_ci'}
430
430
 
@@ -461,7 +461,7 @@ context "A MySQL database with table options" do
461
461
  end
462
462
  end
463
463
 
464
- context "A MySQL database" do
464
+ describe "A MySQL database" do
465
465
  before do
466
466
  @db = MYSQL_DB
467
467
  @db.drop_table(:items) rescue nil
@@ -547,7 +547,7 @@ end
547
547
 
548
548
  # Socket tests should only be run if the MySQL server is on localhost
549
549
  if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.adapter_scheme == :mysql
550
- context "A MySQL database" do
550
+ describe "A MySQL database" do
551
551
  specify "should accept a socket option" do
552
552
  db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
553
553
  proc {db.test_connection}.should_not raise_error
@@ -565,7 +565,7 @@ if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.adapter_sch
565
565
  end
566
566
  end
567
567
 
568
- context "A MySQL database" do
568
+ describe "A MySQL database" do
569
569
  specify "should accept a read_timeout option when connecting" do
570
570
  db = Sequel.connect(MYSQL_DB.opts.merge(:read_timeout=>22342))
571
571
  proc {db.test_connection}.should_not raise_error
@@ -577,7 +577,7 @@ context "A MySQL database" do
577
577
  end
578
578
  end
579
579
 
580
- context "A grouped MySQL dataset" do
580
+ describe "A grouped MySQL dataset" do
581
581
  before do
582
582
  MYSQL_DB[:test2].delete
583
583
  MYSQL_DB[:test2] << {:name => '11', :value => 10}
@@ -599,7 +599,7 @@ context "A grouped MySQL dataset" do
599
599
  end
600
600
  end
601
601
 
602
- context "A MySQL database" do
602
+ describe "A MySQL database" do
603
603
  before do
604
604
  @db = MYSQL_DB
605
605
  @db.drop_table(:posts) rescue nil
@@ -668,7 +668,7 @@ context "A MySQL database" do
668
668
  end
669
669
  end
670
670
 
671
- context "MySQL::Dataset#insert and related methods" do
671
+ describe "MySQL::Dataset#insert and related methods" do
672
672
  before do
673
673
  MYSQL_DB.create_table(:items){String :name; Integer :value}
674
674
  @d = MYSQL_DB[:items]
@@ -853,7 +853,7 @@ context "MySQL::Dataset#insert and related methods" do
853
853
 
854
854
  end
855
855
 
856
- context "MySQL::Dataset#replace" do
856
+ describe "MySQL::Dataset#replace" do
857
857
  before do
858
858
  MYSQL_DB.create_table(:items){Integer :id, :unique=>true; Integer :value}
859
859
  @d = MYSQL_DB[:items]
@@ -895,7 +895,7 @@ context "MySQL::Dataset#replace" do
895
895
  end
896
896
  end
897
897
 
898
- context "MySQL::Dataset#complex_expression_sql" do
898
+ describe "MySQL::Dataset#complex_expression_sql" do
899
899
  before do
900
900
  @d = MYSQL_DB.dataset
901
901
  end
@@ -923,8 +923,35 @@ context "MySQL::Dataset#complex_expression_sql" do
923
923
  end
924
924
  end
925
925
 
926
+ describe "MySQL::Dataset#calc_found_rows" do
927
+ before do
928
+ MYSQL_DB.create_table!(:items){Integer :a}
929
+ end
930
+ after do
931
+ MYSQL_DB.drop_table(:items)
932
+ end
933
+
934
+ specify "should add the SQL_CALC_FOUND_ROWS keyword when selecting" do
935
+ MYSQL_DB[:items].select(:a).calc_found_rows.limit(1).sql.should == \
936
+ 'SELECT SQL_CALC_FOUND_ROWS a FROM items LIMIT 1'
937
+ end
938
+
939
+ specify "should count matching rows disregarding LIMIT clause" do
940
+ MYSQL_DB[:items].multi_insert([{:a => 1}, {:a => 1}, {:a => 2}])
941
+ MYSQL_DB.sqls.clear
942
+
943
+ MYSQL_DB[:items].calc_found_rows.filter(:a => 1).limit(1).all.should == [{:a => 1}]
944
+ MYSQL_DB.dataset.select(:FOUND_ROWS.sql_function.as(:rows)).all.should == [{:rows => 2 }]
945
+
946
+ MYSQL_DB.sqls.should == [
947
+ 'SELECT SQL_CALC_FOUND_ROWS * FROM items WHERE (a = 1) LIMIT 1',
948
+ 'SELECT FOUND_ROWS() AS rows',
949
+ ]
950
+ end
951
+ end
952
+
926
953
  if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc
927
- context "MySQL Stored Procedures" do
954
+ describe "MySQL Stored Procedures" do
928
955
  before do
929
956
  MYSQL_DB.create_table(:items){Integer :id; Integer :value}
930
957
  @d = MYSQL_DB[:items]
@@ -969,7 +996,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc
969
996
  end
970
997
 
971
998
  if MYSQL_DB.adapter_scheme == :mysql
972
- context "MySQL bad date/time conversions" do
999
+ describe "MySQL bad date/time conversions" do
973
1000
  after do
974
1001
  Sequel::MySQL.convert_invalid_date_time = false
975
1002
  end
@@ -1000,7 +1027,7 @@ if MYSQL_DB.adapter_scheme == :mysql
1000
1027
  end
1001
1028
  end
1002
1029
 
1003
- context "MySQL multiple result sets" do
1030
+ describe "MySQL multiple result sets" do
1004
1031
  before do
1005
1032
  MYSQL_DB.create_table!(:a){Integer :a}
1006
1033
  MYSQL_DB.create_table!(:b){Integer :b}
@@ -1,4 +1,5 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
+ require "timeout"
2
3
 
3
4
  unless defined?(ORACLE_DB)
4
5
  ORACLE_DB = Sequel.connect('oracle://hr:hr@localhost/XE')
@@ -32,7 +33,7 @@ ORACLE_DB.create_table :categories do
32
33
  varchar2 :cat_name, :size => 50
33
34
  end
34
35
 
35
- context "An Oracle database" do
36
+ describe "An Oracle database" do
36
37
  specify "should provide disconnect functionality" do
37
38
  ORACLE_DB.execute("select user from dual")
38
39
  ORACLE_DB.pool.size.should == 1
@@ -74,7 +75,7 @@ context "An Oracle database" do
74
75
  end
75
76
  end
76
77
 
77
- context "An Oracle dataset" do
78
+ describe "An Oracle dataset" do
78
79
  before do
79
80
  @d = ORACLE_DB[:items]
80
81
  @d.delete # remove all records
@@ -222,7 +223,7 @@ context "An Oracle dataset" do
222
223
  end
223
224
  end
224
225
 
225
- context "Joined Oracle dataset" do
226
+ describe "Joined Oracle dataset" do
226
227
  before do
227
228
  @d1 = ORACLE_DB[:books]
228
229
  @d1.delete # remove all records
@@ -263,7 +264,7 @@ context "Joined Oracle dataset" do
263
264
  end
264
265
  end
265
266
 
266
- context "Oracle aliasing" do
267
+ describe "Oracle aliasing" do
267
268
  before do
268
269
  @d1 = ORACLE_DB[:books]
269
270
  @d1.delete # remove all records
@@ -284,3 +285,38 @@ context "Oracle aliasing" do
284
285
  @d1.select(:title).group_by(:title).count.should == 2
285
286
  end
286
287
  end
288
+
289
+ describe "Row locks in Oracle" do
290
+ before do
291
+ @d1 = ORACLE_DB[:books]
292
+ @d1.delete # remove all records
293
+ @d1 << {:id => 1, :title => 'aaa'}
294
+ end
295
+
296
+ specify "#for_update should use FOR UPDATE" do
297
+ @d1.for_update.sql.should == "SELECT * FROM BOOKS FOR UPDATE"
298
+ end
299
+
300
+ specify "#lock_style should accept symbols" do
301
+ @d1.lock_style(:update).sql.should == "SELECT * FROM BOOKS FOR UPDATE"
302
+ end
303
+
304
+ specify "should not update during row lock" do
305
+ ORACLE_DB.transaction do
306
+ @d1.filter(:id => 1).for_update.to_a
307
+ proc do
308
+ t1 = Thread.start do
309
+ # wait for unlock
310
+ Timeout::timeout(0.02) do
311
+ ORACLE_DB[:books].filter(:id => 1).update(:title => "bbb")
312
+ end
313
+ end
314
+ t1.join
315
+ end.should raise_error
316
+ @d1.filter(:id => 1).first[:title].should == "aaa"
317
+ end
318
+ t2 = Thread.start { ORACLE_DB[:books].filter(:id => 1).update(:title => "bbb") }
319
+ t2.join
320
+ @d1.filter(:id => 1).first[:title].should == "bbb"
321
+ end
322
+ end