sequel 4.34.0 → 4.35.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +30 -0
  3. data/Rakefile +14 -17
  4. data/doc/object_model.rdoc +4 -4
  5. data/doc/release_notes/4.35.0.txt +130 -0
  6. data/doc/schema_modification.rdoc +8 -3
  7. data/doc/security.rdoc +3 -3
  8. data/lib/sequel/adapters/ado.rb +2 -2
  9. data/lib/sequel/adapters/ado/access.rb +6 -6
  10. data/lib/sequel/adapters/ado/mssql.rb +2 -2
  11. data/lib/sequel/adapters/amalgalite.rb +6 -6
  12. data/lib/sequel/adapters/cubrid.rb +4 -4
  13. data/lib/sequel/adapters/do.rb +2 -2
  14. data/lib/sequel/adapters/do/mysql.rb +1 -1
  15. data/lib/sequel/adapters/do/postgres.rb +1 -1
  16. data/lib/sequel/adapters/do/sqlite3.rb +1 -1
  17. data/lib/sequel/adapters/ibmdb.rb +6 -6
  18. data/lib/sequel/adapters/jdbc.rb +15 -15
  19. data/lib/sequel/adapters/jdbc/db2.rb +1 -1
  20. data/lib/sequel/adapters/jdbc/derby.rb +3 -3
  21. data/lib/sequel/adapters/jdbc/h2.rb +3 -3
  22. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
  23. data/lib/sequel/adapters/jdbc/mssql.rb +1 -1
  24. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  25. data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
  26. data/lib/sequel/adapters/jdbc/postgresql.rb +2 -2
  27. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +1 -1
  28. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
  29. data/lib/sequel/adapters/jdbc/transactions.rb +10 -10
  30. data/lib/sequel/adapters/mock.rb +1 -1
  31. data/lib/sequel/adapters/mysql.rb +2 -2
  32. data/lib/sequel/adapters/mysql2.rb +2 -2
  33. data/lib/sequel/adapters/odbc.rb +2 -2
  34. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  35. data/lib/sequel/adapters/oracle.rb +9 -9
  36. data/lib/sequel/adapters/postgres.rb +3 -3
  37. data/lib/sequel/adapters/shared/mssql.rb +36 -8
  38. data/lib/sequel/adapters/shared/oracle.rb +15 -0
  39. data/lib/sequel/adapters/shared/postgres.rb +22 -1
  40. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  41. data/lib/sequel/adapters/sqlite.rb +7 -7
  42. data/lib/sequel/adapters/swift.rb +3 -3
  43. data/lib/sequel/adapters/swift/mysql.rb +1 -1
  44. data/lib/sequel/adapters/swift/postgres.rb +1 -1
  45. data/lib/sequel/adapters/swift/sqlite.rb +1 -1
  46. data/lib/sequel/adapters/tinytds.rb +5 -7
  47. data/lib/sequel/database/logging.rb +18 -3
  48. data/lib/sequel/database/misc.rb +19 -8
  49. data/lib/sequel/database/schema_generator.rb +7 -2
  50. data/lib/sequel/database/schema_methods.rb +9 -2
  51. data/lib/sequel/database/transactions.rb +52 -18
  52. data/lib/sequel/dataset/actions.rb +24 -19
  53. data/lib/sequel/dataset/features.rb +5 -0
  54. data/lib/sequel/dataset/query.rb +6 -0
  55. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  56. data/lib/sequel/extensions/error_sql.rb +3 -3
  57. data/lib/sequel/extensions/pg_range.rb +10 -1
  58. data/lib/sequel/extensions/schema_dumper.rb +8 -5
  59. data/lib/sequel/extensions/server_logging.rb +61 -0
  60. data/lib/sequel/extensions/sql_comments.rb +91 -0
  61. data/lib/sequel/model/associations.rb +40 -8
  62. data/lib/sequel/model/base.rb +19 -5
  63. data/lib/sequel/plugins/class_table_inheritance.rb +12 -0
  64. data/lib/sequel/plugins/delay_add_association.rb +1 -0
  65. data/lib/sequel/plugins/json_serializer.rb +10 -2
  66. data/lib/sequel/version.rb +1 -1
  67. data/spec/adapter_spec.rb +4 -0
  68. data/spec/adapters/mysql_spec.rb +1 -1
  69. data/spec/adapters/postgres_spec.rb +3 -2
  70. data/spec/core/connection_pool_spec.rb +2 -0
  71. data/spec/core/database_spec.rb +49 -0
  72. data/spec/core/dataset_spec.rb +25 -1
  73. data/spec/core/mock_adapter_spec.rb +3 -1
  74. data/spec/core/schema_generator_spec.rb +1 -1
  75. data/spec/core_model_spec.rb +2 -0
  76. data/spec/core_spec.rb +1 -0
  77. data/spec/extensions/delay_add_association_spec.rb +22 -0
  78. data/spec/extensions/json_serializer_spec.rb +6 -0
  79. data/spec/extensions/pg_range_spec.rb +30 -2
  80. data/spec/extensions/schema_dumper_spec.rb +3 -2
  81. data/spec/extensions/server_logging_spec.rb +45 -0
  82. data/spec/extensions/sql_comments_spec.rb +27 -0
  83. data/spec/files/reversible_migrations/006_reversible.rb +10 -0
  84. data/spec/files/reversible_migrations/007_reversible.rb +10 -0
  85. data/spec/integration/dataset_test.rb +28 -2
  86. data/spec/integration/migrator_test.rb +23 -1
  87. data/spec/integration/schema_test.rb +12 -32
  88. data/spec/integration/transaction_test.rb +10 -0
  89. data/spec/integration/type_test.rb +1 -1
  90. data/spec/model/eager_loading_spec.rb +16 -0
  91. data/spec/model/record_spec.rb +9 -0
  92. data/spec/model_no_assoc_spec.rb +1 -0
  93. data/spec/model_spec.rb +1 -0
  94. data/spec/plugin_spec.rb +1 -0
  95. metadata +16 -2
@@ -0,0 +1,61 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The server_logging extension makes the logger include the server/shard
4
+ # the query was issued on. This makes it easier to use the logs when
5
+ # using sharding.
6
+ #
7
+ # Example:
8
+ #
9
+ # DB.opts[:server]
10
+ # # {:read_only=>{}, :b=>{}}
11
+ # DB.extension :server_logging
12
+ # DB[:a].all
13
+ # # (0.000005s) (conn: 1014942550, server: read_only) SELECT * FROM a
14
+ # DB[:a].server(:b).all
15
+ # # (0.000004s) (conn: 997304100, server: b) SELECT * FROM a
16
+ # DB[:a].insert
17
+ # # (0.000004s) (conn: 1014374750, server: default) INSERT INTO a DEFAULT VALUES
18
+ #
19
+ # In order for the server/shard to be correct for all connections, you need to
20
+ # use this before connections to the database are made, or you need to call
21
+ # <tt>Database#disconnect</tt> after loading this extension.
22
+ #
23
+ # Related module: Sequel::ServerLogging
24
+
25
+ #
26
+ module Sequel
27
+ module ServerLogging
28
+ # Initialize the hash mapping connections to shards, and turn on logging
29
+ # of connection info unless it has specifically been turned off.
30
+ def self.extended(db)
31
+ db.instance_exec do
32
+ @server_connection_map ||= {}
33
+ self.log_connection_info = true if log_connection_info.nil?
34
+ end
35
+ end
36
+
37
+ # When setting up a new connection, associate the connection with the
38
+ # shard.
39
+ def connect(server)
40
+ conn = super
41
+ Sequel.synchronize{@server_connection_map[conn] = server}
42
+ conn
43
+ end
44
+
45
+ # When disconnecting a connection, remove the related connection from the mapping.
46
+ def disconnect_connection(conn)
47
+ super
48
+ ensure
49
+ Sequel.synchronize{@server_connection_map.delete(conn)}
50
+ end
51
+
52
+ private
53
+
54
+ # Include the server with the connection's id.
55
+ def connection_info(conn)
56
+ "(conn: #{conn.__id__}, server: #{Sequel.synchronize{@server_connection_map[conn]}}) "
57
+ end
58
+ end
59
+
60
+ Database.register_extension(:server_logging, ServerLogging)
61
+ end
@@ -0,0 +1,91 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The sql_comments extension adds Dataset#comment to the datasets,
4
+ # allowing you to set SQL comments in the resulting query. These
5
+ # comments are appended to the end of the SQL query:
6
+ #
7
+ # ds = DB[:table].comment("Some Comment").all
8
+ # # SELECT * FROM table -- Some Comment
9
+ # #
10
+ #
11
+ # As you can see, this uses single line SQL comments (--) suffixed
12
+ # by a newline. This # plugin transforms all consecutive
13
+ # whitespace in the comment to # a single string:
14
+ #
15
+ # ds = DB[:table].comment("Some\r\nComment Here").all
16
+ # # SELECT * FROM table -- Some Comment Here
17
+ # #
18
+ #
19
+ # The reason for the prefixing and suffixing by newlines is to
20
+ # work correctly when used in subqueries:
21
+ #
22
+ # ds = DB[:table].comment("Some\r\nComment Here")
23
+ # ds.where(:id=>ds).all
24
+ # # SELECT * FROM table WHERE (id IN (SELECT * FROM table -- Some Comment Here
25
+ # # )) -- Some Comment Here
26
+ # #
27
+ #
28
+ # In addition to working on SELECT queries, it also works when
29
+ # inserting, updating, and deleting.
30
+ #
31
+ # Due to the use of single line SQL comments and converting all
32
+ # whitespace to spaces, this should correctly handle even
33
+ # malicious input. However, it would be unwise to rely on that,
34
+ # you should probably attempt to ensure that the argument given
35
+ # to Dataset#comment is not derived from user input.
36
+ #
37
+ # You can load this extension into specific datasets:
38
+ #
39
+ # ds = DB[:table]
40
+ # ds = ds.extension(:sql_comments)
41
+ #
42
+ # Or you can load it into all of a database's datasets, which
43
+ # is probably the desired behavior if you are using this extension:
44
+ #
45
+ # DB.extension(:sql_comments)
46
+ #
47
+ # Note that Microsoft Access does not support inline comments,
48
+ # and attempting to use comments on it will result in SQL syntax
49
+ # errors.
50
+ #
51
+ # Related module: Sequel::SQLComments
52
+
53
+ #
54
+ module Sequel
55
+ module SQLComments
56
+ # Return a modified copy of the dataset that will use the given comment.
57
+ # To uncomment a commented dataset, pass nil as the argument.
58
+ def comment(comment)
59
+ clone(:comment=>(format_sql_comment(comment) if comment))
60
+ end
61
+
62
+ %w'select insert update delete'.each do |type|
63
+ define_method(:"#{type}_sql") do |*a|
64
+ sql = super(*a)
65
+ if comment = @opts[:comment]
66
+ # This assumes that the comment stored in the dataset has
67
+ # already been formatted. If not, this could result in SQL
68
+ # injection.
69
+ #
70
+ # Additionally, due to the use of an SQL comment, if any
71
+ # SQL is appened to the query after the comment is added,
72
+ # it will become part of the comment unless it is preceded
73
+ # by a newline.
74
+ sql << comment
75
+ end
76
+ sql
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Format the comment. For maximum compatibility, this uses a
83
+ # single line SQL comment, and converts all consecutive whitespace
84
+ # in the comment to a single space.
85
+ def format_sql_comment(comment)
86
+ " -- #{comment.to_s.gsub(/\s+/, ' ')}\n"
87
+ end
88
+ end
89
+
90
+ Dataset.register_extension(:sql_comments, SQLComments)
91
+ end
@@ -2194,14 +2194,7 @@ module Sequel
2194
2194
 
2195
2195
  # Add the given associated object to the given association
2196
2196
  def add_associated_object(opts, o, *args)
2197
- klass = opts.associated_class
2198
- if o.is_a?(Hash)
2199
- o = klass.new(o)
2200
- elsif o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
2201
- o = klass.with_pk!(o)
2202
- elsif !o.is_a?(klass)
2203
- raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
2204
- end
2197
+ o = make_add_associated_object(opts, o)
2205
2198
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2206
2199
  ensure_associated_primary_key(opts, o, *args)
2207
2200
  return if run_association_callbacks(opts, :before_add, o) == false
@@ -2317,6 +2310,25 @@ module Sequel
2317
2310
  opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == opts.associated_class.primary_key}
2318
2311
  end
2319
2312
 
2313
+ # Convert the input of the add_* association method into an associated object. For
2314
+ # hashes, this creates a new object using the hash. For integers, strings, and arrays,
2315
+ # assume the value specifies a primary key, and lookup an existing object with that primary key.
2316
+ # Otherwise, if the object is not already an instance of the class, raise an exception.
2317
+ def make_add_associated_object(opts, o)
2318
+ klass = opts.associated_class
2319
+
2320
+ case o
2321
+ when Hash
2322
+ klass.new(o)
2323
+ when Integer, String, Array
2324
+ klass.with_pk!(o)
2325
+ when klass
2326
+ o
2327
+ else
2328
+ raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
2329
+ end
2330
+ end
2331
+
2320
2332
  # Remove all associated objects from the given association
2321
2333
  def remove_all_associated_objects(opts, *args)
2322
2334
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
@@ -2650,6 +2662,26 @@ END
2650
2662
  end
2651
2663
  end
2652
2664
 
2665
+ # If the dataset is being eagerly loaded, default to calling all
2666
+ # instead of each.
2667
+ def to_hash(key_column=nil, value_column=nil, opts=OPTS)
2668
+ if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
2669
+ opts = Hash[opts]
2670
+ opts[:all] = true
2671
+ end
2672
+ super
2673
+ end
2674
+
2675
+ # If the dataset is being eagerly loaded, default to calling all
2676
+ # instead of each.
2677
+ def to_hash_groups(key_column, value_column=nil, opts=OPTS)
2678
+ if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
2679
+ opts = Hash[opts]
2680
+ opts[:all] = true
2681
+ end
2682
+ super
2683
+ end
2684
+
2653
2685
  # Do not attempt to split the result set into associations,
2654
2686
  # just return results as simple objects. This is useful if you
2655
2687
  # want to use eager_graph as a shortcut to have all of the joins
@@ -1402,17 +1402,31 @@ module Sequel
1402
1402
  @values.keys
1403
1403
  end
1404
1404
 
1405
- # Refresh this record using +for_update+ unless this is a new record. Returns self.
1406
- # This can be used to make sure no other process is updating the record at the
1407
- # same time.
1405
+ # Refresh this record using +for_update+ (by default, or the specified style when given)
1406
+ # unless this is a new record. Returns self. This can be used to make sure no other
1407
+ # process is updating the record at the same time.
1408
+ #
1409
+ # If style is a string, it will be used directly. You should never pass a string
1410
+ # to this method that is derived from user input, as that can lead to
1411
+ # SQL injection.
1412
+ #
1413
+ # A symbol may be used for database independent locking behavior, but
1414
+ # all supported symbols have separate methods (e.g. for_update).
1415
+ #
1408
1416
  #
1409
1417
  # a = Artist[1]
1410
1418
  # Artist.db.transaction do
1411
1419
  # a.lock!
1412
1420
  # a.update(:name=>'A')
1413
1421
  # end
1414
- def lock!
1415
- _refresh(this.for_update) unless new?
1422
+ #
1423
+ # a = Artist[2]
1424
+ # Artist.db.transaction do
1425
+ # a.lock!('FOR NO KEY UPDATE')
1426
+ # a.update(:name=>'B')
1427
+ # end
1428
+ def lock!(style=:update)
1429
+ _refresh(this.lock_style(style)) unless new?
1416
1430
  self
1417
1431
  end
1418
1432
 
@@ -84,6 +84,18 @@ module Sequel
84
84
  # a.values # {:id=>1, name=>'S', :kind=>'CEO'}
85
85
  # a.refresh.values # {:id=>1, name=>'S', :kind=>'Executive', :num_staff=>4, :num_managers=>2}
86
86
  #
87
+ # You can also load directly from a subclass:
88
+ #
89
+ # a = Executive.first
90
+ # a.values # {:id=>1, name=>'S', :kind=>'Executive', :num_staff=>4, :num_managers=>2}
91
+ #
92
+ # Note that when loading from a subclass, because the subclass dataset uses a JOIN,
93
+ # if you are referencing the primary key column, you need to disambiguate the reference
94
+ # by explicitly qualifying it:
95
+ #
96
+ # a = Executive.where(:id=>1).first # database error
97
+ # a = Executive.where(:executives__id=>1).first # no error
98
+ #
87
99
  # = Usage
88
100
  #
89
101
  # # Use the default of storing the class name in the sti_key
@@ -38,6 +38,7 @@ module Sequel
38
38
  # current object.
39
39
  def add_associated_object(opts, o, *args)
40
40
  if opts.dataset_need_primary_key? && new?
41
+ o = make_add_associated_object(opts, o)
41
42
  delay_validate_associated_object(opts, o)
42
43
  send(opts[:name]) << o
43
44
  after_create_hook{super(opts, o, *args)}
@@ -300,8 +300,16 @@ module Sequel
300
300
  if inc.is_a?(Hash)
301
301
  inc.each do |k, v|
302
302
  v = v.empty? ? [] : [v]
303
- h[k.to_s] = case objs = send(k)
304
- when Array
303
+
304
+ objs = send(k)
305
+
306
+ is_array = if r = model.association_reflection(k)
307
+ r.returns_array?
308
+ else
309
+ objs.is_a?(Array)
310
+ end
311
+
312
+ h[k.to_s] = if is_array
305
313
  objs.map{|obj| Literal.new(Sequel.object_to_json(obj, *v))}
306
314
  else
307
315
  Literal.new(Sequel.object_to_json(objs, *v))
@@ -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 = 34
8
+ MINOR = 35
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
@@ -0,0 +1,4 @@
1
+ if !ARGV.empty? && ARGV.first != 'none'
2
+ require "./spec/adapters/#{ARGV.first}_spec.rb"
3
+ end
4
+ Dir['./spec/integration/*_test.rb'].each{|f| require f}
@@ -528,7 +528,7 @@ describe "A MySQL database" do
528
528
 
529
529
  it "should have set_column_type support keep existing options" do
530
530
  @db.create_table(:items){Integer :id, :null=>false, :default=>5}
531
- @db.alter_table(:items){set_column_type :id, Bignum}
531
+ @db.alter_table(:items){set_column_type :id, :Bignum}
532
532
  check_sqls do
533
533
  @db.sqls.must_equal ["CREATE TABLE `items` (`id` integer NOT NULL DEFAULT 5)", "DESCRIBE `items`", "ALTER TABLE `items` CHANGE COLUMN `id` `id` bigint NOT NULL DEFAULT 5"]
534
534
  end
@@ -945,7 +945,7 @@ describe "A PostgreSQL database" do
945
945
  @db.create_table!(:posts){primary_key :a, :type=>Fixnum}
946
946
  @db[:posts].insert.must_equal 1
947
947
  @db[:posts].insert.must_equal 2
948
- @db.create_table!(:posts){primary_key :a, :type=>Bignum}
948
+ @db.create_table!(:posts){primary_key :a, :type=>:Bignum}
949
949
  @db[:posts].insert.must_equal 1
950
950
  @db[:posts].insert.must_equal 2
951
951
  end
@@ -973,6 +973,7 @@ describe "A PostgreSQL database" do
973
973
  @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
974
974
 
975
975
  @db[:posts].full_text_search(:title, 'rails').all.must_equal [{:title=>'ruby rails', :body=>'yowsa'}]
976
+ @db[:posts].full_text_search(:title, 'rails', :headline=>true).all.must_equal [{:title=>'ruby rails', :body=>'yowsa', :headline=>'ruby <b>rails</b>'}]
976
977
  @db[:posts].full_text_search([:title, :body], ['yowsa', 'rails']).all.must_equal [:title=>'ruby rails', :body=>'yowsa']
977
978
  @db[:posts].full_text_search(:title, 'scooby', :language => 'french').all.must_equal [{:title=>'ruby scooby', :body=>'x'}]
978
979
 
@@ -997,7 +998,7 @@ describe "A PostgreSQL database" do
997
998
  @db[:posts].insert(:title=>t1)
998
999
  @db[:posts].insert(:title=>t2)
999
1000
  @db[:posts].full_text_search(:title, 'ruby & sequel', :rank=>true).select_map(:title).must_equal [t2, t1]
1000
- end
1001
+ end if DB.server_version >= 80300
1001
1002
 
1002
1003
  it "should support spatial indexes" do
1003
1004
  @db.create_table(:posts){box :geom; spatial_index [:geom]}
@@ -8,6 +8,8 @@ mock_db = lambda do |*a, &b|
8
8
  if b2 = a.shift
9
9
  (class << db; self end).send(:define_method, :disconnect_connection){|c| b2.arity == 1 ? b2.call(c) : b2.call}
10
10
  end
11
+ # Work around JRuby Issue #3854
12
+ (class << db; self end).send(:public, :connect, :disconnect_connection)
11
13
  db
12
14
  end
13
15
 
@@ -341,6 +341,35 @@ describe "Database#log_yield" do
341
341
  end
342
342
  end
343
343
 
344
+ describe "Database#log_connection_yield" do
345
+ before do
346
+ @o = Object.new
347
+ def @o.logs; @logs || []; end
348
+ def @o.to_ary; [self]; end
349
+ def @o.method_missing(*args); (@logs ||= []) << args; end
350
+ @conn = Object.new
351
+ @db = Sequel::Database.new(:logger=>@o)
352
+ end
353
+
354
+ it "should log SQL to the loggers" do
355
+ @db.log_connection_yield("some SQL", @conn){}
356
+ @o.logs.length.must_equal 1
357
+ @o.logs.first.length.must_equal 2
358
+ @o.logs.first.first.must_equal :info
359
+ @o.logs.first.last.must_match(/some SQL\z/)
360
+ @o.logs.first.last.wont_match(/\(conn: -?\d+\) some SQL\z/)
361
+ end
362
+
363
+ it "should include connection information when logging" do
364
+ @db.log_connection_info = true
365
+ @db.log_connection_yield("some SQL", @conn){}
366
+ @o.logs.length.must_equal 1
367
+ @o.logs.first.length.must_equal 2
368
+ @o.logs.first.first.must_equal :info
369
+ @o.logs.first.last.must_match(/\(conn: -?\d+\) some SQL\z/)
370
+ end
371
+ end
372
+
344
373
  describe "Database#uri" do
345
374
  before do
346
375
  @c = Class.new(Sequel::Database) do
@@ -993,6 +1022,15 @@ describe "Database#transaction with savepoint support" do
993
1022
  a.must_equal [1, 1]
994
1023
  end
995
1024
 
1025
+ it "should automatically use a savepoint if :rollback=>:always given inside a transaction" do
1026
+ @db.transaction do
1027
+ @db.transaction(:rollback=>:always) do
1028
+ @db.get(1)
1029
+ end
1030
+ end
1031
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "SELECT 1 AS v LIMIT 1", "ROLLBACK TO SAVEPOINT autopoint_1", "COMMIT"]
1032
+ end
1033
+
996
1034
  it "should support :retry_on option for automatically retrying transactions inside an :auto_savepoint transaction" do
997
1035
  a = []
998
1036
  @db.transaction(:auto_savepoint=>true) do
@@ -1058,6 +1096,17 @@ describe "Database#transaction without savepoint support" do
1058
1096
  @db.sqls.must_equal ['BEGIN', 'COMMIT']
1059
1097
  end
1060
1098
 
1099
+ it "should automatically use a savepoint if :rollback=>:always given inside a transaction" do
1100
+ proc do
1101
+ @db.transaction do
1102
+ @db.transaction(:rollback=>:always) do
1103
+ @db.get(1)
1104
+ end
1105
+ end
1106
+ end.must_raise Sequel::Error
1107
+ @db.sqls.must_equal ["BEGIN", "ROLLBACK"]
1108
+ end
1109
+
1061
1110
  include DatabaseTransactionSpecs
1062
1111
  end
1063
1112
 
@@ -1822,10 +1822,17 @@ describe "Dataset#to_hash_groups" do
1822
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}])
1823
1823
  end
1824
1824
 
1825
- it "should accept an optional :hash parameter into which entries can be merged" do
1825
+ it "should accept a :hash option into which entries can be merged" do
1826
1826
  @d.to_hash_groups(:a, :b, :hash => (tmp = {})).must_be_same_as(tmp)
1827
1827
  end
1828
1828
 
1829
+ it "should accept an :all option to use all into which entries can be merged" do
1830
+ called = false
1831
+ meta_def(@d, :post_load){|_| called = true}
1832
+ @d.to_hash_groups(:a, :b, :all=>true)
1833
+ called.must_equal true
1834
+ end
1835
+
1829
1836
  it "should not call the row_proc if two arguments are given" do
1830
1837
  @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1831
1838
  @d.to_hash_groups(:a, :b).must_equal(1 => [2, 6], 3 => [4], 7 => [4])
@@ -4534,6 +4541,23 @@ describe "Dataset#lock_style and for_update" do
4534
4541
  end
4535
4542
  end
4536
4543
 
4544
+ describe "Dataset#skip_locked" do
4545
+ before do
4546
+ @ds = Sequel.mock.dataset.from(:t).for_update
4547
+ end
4548
+
4549
+ it "should raise an error if not supported" do
4550
+ proc{@ds.skip_locked}.must_raise Sequel::Error
4551
+ end
4552
+
4553
+ it "should skipped locked rows if supported" do
4554
+ def @ds.supports_skip_locked?; true end
4555
+ def @ds.select_lock_sql(sql) super; sql << " SKIP LOCKED" if @opts[:skip_locked] end
4556
+ @ds.sql.must_equal "SELECT * FROM t FOR UPDATE"
4557
+ @ds.skip_locked.sql.must_equal "SELECT * FROM t FOR UPDATE SKIP LOCKED"
4558
+ end
4559
+ end
4560
+
4537
4561
  describe "Custom ASTTransformer" do
4538
4562
  it "should transform given objects" do
4539
4563
  c = Class.new(Sequel::ASTTransformer) do