sequel 3.20.0 → 3.21.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 (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