sequel 5.70.0 → 5.80.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +134 -0
  3. data/README.rdoc +7 -5
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +1 -1
  6. data/doc/migration.rdoc +15 -0
  7. data/doc/opening_databases.rdoc +6 -2
  8. data/doc/querying.rdoc +6 -1
  9. data/doc/release_notes/5.71.0.txt +21 -0
  10. data/doc/release_notes/5.72.0.txt +33 -0
  11. data/doc/release_notes/5.73.0.txt +66 -0
  12. data/doc/release_notes/5.74.0.txt +45 -0
  13. data/doc/release_notes/5.75.0.txt +35 -0
  14. data/doc/release_notes/5.76.0.txt +86 -0
  15. data/doc/release_notes/5.77.0.txt +63 -0
  16. data/doc/release_notes/5.78.0.txt +67 -0
  17. data/doc/release_notes/5.79.0.txt +28 -0
  18. data/doc/release_notes/5.80.0.txt +40 -0
  19. data/doc/schema_modification.rdoc +2 -2
  20. data/doc/testing.rdoc +4 -2
  21. data/lib/sequel/adapters/ibmdb.rb +1 -1
  22. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  23. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  24. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  25. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  26. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  27. data/lib/sequel/adapters/jdbc.rb +10 -6
  28. data/lib/sequel/adapters/mysql2.rb +2 -2
  29. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  30. data/lib/sequel/adapters/postgres.rb +6 -5
  31. data/lib/sequel/adapters/shared/db2.rb +12 -0
  32. data/lib/sequel/adapters/shared/mssql.rb +30 -2
  33. data/lib/sequel/adapters/shared/mysql.rb +68 -3
  34. data/lib/sequel/adapters/shared/oracle.rb +4 -6
  35. data/lib/sequel/adapters/shared/postgres.rb +116 -6
  36. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -4
  37. data/lib/sequel/adapters/shared/sqlite.rb +20 -3
  38. data/lib/sequel/adapters/sqlite.rb +42 -3
  39. data/lib/sequel/connection_pool.rb +4 -2
  40. data/lib/sequel/database/misc.rb +3 -2
  41. data/lib/sequel/database/schema_methods.rb +11 -4
  42. data/lib/sequel/database/transactions.rb +6 -0
  43. data/lib/sequel/dataset/actions.rb +8 -6
  44. data/lib/sequel/dataset/dataset_module.rb +1 -1
  45. data/lib/sequel/dataset/features.rb +10 -1
  46. data/lib/sequel/dataset/graph.rb +1 -0
  47. data/lib/sequel/dataset/query.rb +58 -9
  48. data/lib/sequel/dataset/sql.rb +47 -34
  49. data/lib/sequel/exceptions.rb +5 -0
  50. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  51. data/lib/sequel/extensions/async_thread_pool.rb +7 -0
  52. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  53. data/lib/sequel/extensions/caller_logging.rb +2 -1
  54. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  55. data/lib/sequel/extensions/index_caching.rb +5 -1
  56. data/lib/sequel/extensions/migration.rb +64 -14
  57. data/lib/sequel/extensions/named_timezones.rb +1 -1
  58. data/lib/sequel/extensions/pg_array.rb +10 -0
  59. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  60. data/lib/sequel/extensions/pg_extended_date_support.rb +4 -4
  61. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  62. data/lib/sequel/extensions/pg_range.rb +2 -2
  63. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  64. data/lib/sequel/extensions/provenance.rb +108 -0
  65. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  66. data/lib/sequel/extensions/schema_caching.rb +1 -1
  67. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  68. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  69. data/lib/sequel/model/associations.rb +9 -2
  70. data/lib/sequel/model/base.rb +26 -13
  71. data/lib/sequel/model/exceptions.rb +15 -3
  72. data/lib/sequel/plugins/column_encryption.rb +27 -6
  73. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  74. data/lib/sequel/plugins/list.rb +5 -2
  75. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  76. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  77. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  78. data/lib/sequel/plugins/paged_operations.rb +181 -0
  79. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +5 -1
  80. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  81. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  82. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  83. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  84. data/lib/sequel/version.rb +1 -1
  85. metadata +44 -3
@@ -0,0 +1,67 @@
1
+ = New Features
2
+
3
+ * SQLite 3.45+ jsonb functions are now supported in the sqlite_json_ops
4
+ extension. Similar to the postgres_json_ops extension, there are
5
+ now separate methods for dealing with json and jsonb types:
6
+
7
+ Sequel.sqlite_json_op(:column) # json
8
+ Sequel.sqlite_jsonb_op(:column) # jsonb
9
+
10
+ Some methods that use json_* functions for json ops use jsonb_*
11
+ functions for jsonb ops:
12
+
13
+ jb = Sequel.sqlite_jsonb_op(:column)
14
+ jb.extract('$.a') # jsonb_extract(column, '$.a')
15
+ jb.insert('$.a', 1) # jsonb_insert(column, '$.a', 1)
16
+ jb.set('$.a', 1) # jsonb_set(column, '$.a', 1)
17
+ jb.replace('$.a', 1) # jsonb_replace(column, '$.a', 1)
18
+ jb.remove('$.a') # jsonb_remove(column, '$.a')
19
+ jb.patch('{"a":2}') # jsonb_patch(column, '{"a":2}')
20
+
21
+ You can use the json and jsonb methods to convert jsonb to json
22
+ and json to jsonb, respectively.
23
+
24
+ jb.json # json(column)
25
+
26
+ Use of the json method on jsonb types is important, because if you
27
+ want to be able to deal with the values in Ruby, you must convert
28
+ the jsonb value to json in the database before the database returns
29
+ the value. Unlike PostgreSQL, SQLite will not convert the value
30
+ from jsonb to json on retrieval, and direct use of SQLite's jsonb
31
+ format is unsupported by SQLite as it is subject to change.
32
+
33
+ * Database#with_advisory_lock is now supported on PostgreSQL, MySQL,
34
+ and Microsoft SQL Server. This supports advisory (explicit)
35
+ locking, using the database-specific APIs. To work on all three
36
+ servers, lock ids should be integers in the signed 64-bit range.
37
+
38
+ DB.with_advisory_lock(1234) do
39
+ # do something
40
+ end
41
+
42
+ By default, an AdvisoryLockError is raised if the lock cannot be
43
+ immediately acquired. You can use the :wait option to wait until
44
+ the lock can be acquired, instead of raising.
45
+
46
+ DB.with_advisory_lock(1234, wait: true) do
47
+ # do something
48
+ end
49
+
50
+ * Migrator.run now supports a :use_advisory_lock option to use
51
+ advisory locks when running migrations, so that it does not
52
+ attempt to run the same migration more than once in the case
53
+ where multiple processes are running the migrator simultaneously.
54
+ It's probably best to avoid running the migrator in multiple
55
+ processes simultaneously instead of relying on this option.
56
+
57
+ = Other Improvements
58
+
59
+ * Database#values now supports chaining with compounds on
60
+ PostgreSQL.
61
+
62
+ DB.values([[1, 2]]).union(DB.values([[3, 4]]))
63
+ # SELECT * FROM (VALUES (1, 2) UNION (VALUES (3, 4))) AS t1
64
+
65
+ * The internal hash used to store transaction metadata now uses
66
+ compare_by_identity, which is faster and avoids potential
67
+ issues if a driver implements connection object equality.
@@ -0,0 +1,28 @@
1
+ = New Features
2
+
3
+ * Dataset#select_prepend has been added for prepending to the
4
+ currently selected columns:
5
+
6
+ DB[:table].select_prepend(:column)
7
+ # SELECT column, table.*
8
+
9
+ As not all databases support "SELECT column, *", select_prepend
10
+ qualifies wildcard selections to all tables referenced in the
11
+ query.
12
+
13
+ The only reason to use select_prepend is if you want the hashes
14
+ returned by Sequel to be in a specific order. Otherwise, it is
15
+ better to use select_append.
16
+
17
+ * On PostgreSQL, Sequel now supports an :unlogged_tables_default
18
+ Database option, which will default created tables to be UNLOGGED.
19
+ This can be useful to speedup testing in some cases, but it should
20
+ never be used in cases where data integrity is important.
21
+
22
+ = Other Improvements
23
+
24
+ * On PostgreSQL, Database#create_or_replace_view now supports the
25
+ :materialized option. This allows for dropping an existing
26
+ materialized view and creating a new one with the same name
27
+ (PostgreSQL does not have native support for replacing materialized
28
+ views).
@@ -0,0 +1,40 @@
1
+ = New Features
2
+
3
+ * A provenance dataset extension has been added. This extension makes
4
+ SQL queries include a comment describing how the dataset was built.
5
+ This can make debugging complex cases significantly easier. Here's
6
+ a simple example:
7
+
8
+ DB.extension :provenance
9
+
10
+ DB[:table].
11
+ select(:a).
12
+ where{b > 10}.
13
+ order(:c).
14
+ limit(10)
15
+ # SQL:
16
+ # SELECT a FROM table WHERE (b > 10) ORDER BY c LIMIT 10 --
17
+ # -- Dataset Provenance
18
+ # -- Keys:[:from] Source:(eval at bin/sequel:257):2:in `<main>'
19
+ # -- Keys:[:select] Source:(eval at bin/sequel:257):3:in `<main>'
20
+ # -- Keys:[:where] Source:(eval at bin/sequel:257):4:in `<main>'
21
+ # -- Keys:[:order] Source:(eval at bin/sequel:257):5:in `<main>'
22
+ # -- Keys:[:limit] Source:(eval at bin/sequel:257):6:in `<main>'
23
+
24
+ With the above example, it's obvious how the dataset is created, but
25
+ but in real applications, where datasets can be built from multiple
26
+ files, seeing where each dataset clone was made can be helpful.
27
+
28
+ The source listed will skip locations in the Ruby standard library
29
+ as well as Sequel itself. Other locations can be skipped by
30
+ providing a Database :provenance_caller_ignore Regexp option:
31
+
32
+ DB.opts[:provenance_caller_ignore] = /\/gems\/library_name-/
33
+
34
+ = Other Improvements
35
+
36
+ * For dataset methods where Sequel can determine that the return
37
+ value would be equivalent to the receiver, Sequel now returns the
38
+ receiver. This reduces the number of dataset allocations.
39
+
40
+ * Sequel now supports Dataset#skip_locked on MariaDB 10.6+.
@@ -81,8 +81,8 @@ appropriate 64-bit integer type for the database you are using.
81
81
 
82
82
  === Column options
83
83
 
84
- When using the type name as method, the third argument is an options hash, and when using the +column+
85
- method, the fourth argument is the options hash. The following options are supported:
84
+ When using the type name as method, the second argument is an options hash, and when using the +column+
85
+ method, the third argument is the options hash. The following options are supported:
86
86
 
87
87
  :default :: The default value for the column.
88
88
  :index :: Create an index on this column. If given a hash, use the hash as the
data/doc/testing.rdoc CHANGED
@@ -159,12 +159,13 @@ The SEQUEL_INTEGRATION_URL environment variable specifies the Database connectio
159
159
 
160
160
  === Other
161
161
 
162
+ SEQUEL_AUTO_CAST_DATE_TIME :: Use the auto_cast_date_and_time extension when running the specs
162
163
  SEQUEL_ASYNC_THREAD_POOL :: Use the async_thread_pool extension when running the specs
163
164
  SEQUEL_ASYNC_THREAD_POOL_PREEMPT :: Use the async_thread_pool extension when running the specs, with the :preempt_async_thread option
164
165
  SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
165
166
  SEQUEL_COLUMNS_INTROSPECTION :: Use the columns_introspection extension when running the specs
166
167
  SEQUEL_CONCURRENT_EAGER_LOADING :: Use the async_thread_pool extension and concurrent_eager_loading plugin when running the specs
167
- SEQUEL_CONNECTION_VALIDATOR :: Use the connection validator extension when running the specs
168
+ SEQUEL_CONNECTION_VALIDATOR :: Use the connection_validator extension when running the adapter/integration specs
168
169
  SEQUEL_DUPLICATE_COLUMNS_HANDLER :: Use the duplicate columns handler extension with value given when running the specs
169
170
  SEQUEL_ERROR_SQL :: Use the error_sql extension when running the specs
170
171
  SEQUEL_FIBER_CONCURRENCY :: Use the fiber_concurrency extension when running the adapter and integration specs
@@ -176,7 +177,7 @@ SEQUEL_MODEL_PREPARED_STATEMENTS :: Use the prepared_statements plugin when runn
176
177
  SEQUEL_MODEL_THROW_FAILURES :: Use the throw_failures plugin when running the specs
177
178
  SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running the specs
178
179
  SEQUEL_NO_PENDING :: Don't skip any specs, try running all specs (note, can cause lockups for some adapters)
179
- SEQUEL_PG_AUTO_PARAMETERIZE :: Use the pg_auto_parameterize extension when running the postgres specs
180
+ SEQUEL_PG_AUTO_PARAMETERIZE :: Use the pg_auto_parameterize extension when running the postgres specs. Value can be +in_array+ to test the pg_auto_parameterize_in_array extension, and +in_array_string+ to test the pg_auto_parameterize_in_array extension with the +:treat_in_string_list_as_text_array+ Database option set.
180
181
  SEQUEL_PG_TIMESTAMPTZ :: Use the pg_timestamptz extension when running the postgres specs
181
182
  SEQUEL_PRIMARY_KEY_LOOKUP_CHECK_VALUES :: Use the primary_key_lookup_check_values extension when running the adapter or integration specs
182
183
  SEQUEL_QUERY_PER_ASSOCIATION_DB_0_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
@@ -185,4 +186,5 @@ SEQUEL_QUERY_PER_ASSOCIATION_DB_2_URL :: Run query-per-association integration t
185
186
  SEQUEL_QUERY_PER_ASSOCIATION_DB_3_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
186
187
  SEQUEL_SPLIT_SYMBOLS :: Turn on symbol splitting when running the adapter and integration specs
187
188
  SEQUEL_SYNCHRONIZE_SQL :: Use the synchronize_sql extension when running the specs
189
+ SEQUEL_TRANSACTION_CONNECTION_VALIDATOR :: Use the transaction_connection_validator extension when running the adapter/integration specs
188
190
  SEQUEL_TZINFO_VERSION :: Force the given tzinfo version when running the specs (e.g. '>=2')
@@ -301,7 +301,7 @@ module Sequel
301
301
  end
302
302
 
303
303
  def database_exception_sqlstate(exception, opts)
304
- exception.sqlstate
304
+ exception.sqlstate if exception.respond_to?(:sqlstate)
305
305
  end
306
306
 
307
307
  def dataset_class_default
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  Sequel::JDBC.load_driver('org.h2.Driver', :H2)
4
+ require_relative '../../extensions/auto_cast_date_and_time'
4
5
 
5
6
  module Sequel
6
7
  module JDBC
@@ -14,6 +15,8 @@ module Sequel
14
15
 
15
16
  module H2
16
17
  module DatabaseMethods
18
+ include AutoCastDateAndTime
19
+
17
20
  def commit_prepared_transaction(transaction_id, opts=OPTS)
18
21
  run("COMMIT TRANSACTION #{transaction_id}", opts)
19
22
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  Sequel::JDBC.load_driver('org.hsqldb.jdbcDriver', :HSQLDB)
4
4
  require_relative 'transactions'
5
+ require_relative '../../extensions/auto_cast_date_and_time'
5
6
 
6
7
  module Sequel
7
8
  module JDBC
@@ -15,6 +16,7 @@ module Sequel
15
16
 
16
17
  module HSQLDB
17
18
  module DatabaseMethods
19
+ include AutoCastDateAndTime
18
20
  include ::Sequel::JDBC::Transactions
19
21
 
20
22
  def database_type
@@ -199,6 +199,7 @@ module Sequel
199
199
  v.strftime("'%H:%M:%S#{sprintf(".%03d", (v.usec/1000.0).round)}'")
200
200
  end
201
201
 
202
+ INTEGER_TYPE = Java::JavaSQL::Types::INTEGER
202
203
  STRING_TYPE = Java::JavaSQL::Types::VARCHAR
203
204
  ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
204
205
  PG_SPECIFIC_TYPES = [Java::JavaSQL::Types::ARRAY, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
@@ -219,6 +220,8 @@ module Sequel
219
220
  oid = meta.getField(i).getOID
220
221
  if pr = db.oid_convertor_proc(oid)
221
222
  pr
223
+ elsif oid == 28 # XID (Transaction ID)
224
+ map[INTEGER_TYPE]
222
225
  elsif oid == 2950 # UUID
223
226
  map[STRING_TYPE]
224
227
  elsif meta.getPGType(i) == 'hstore'
@@ -36,6 +36,10 @@ module Sequel
36
36
 
37
37
  private
38
38
 
39
+ def database_exception_use_sqlstates?
40
+ false
41
+ end
42
+
39
43
  # Use @@IDENTITY to get the last inserted id
40
44
  def last_insert_id(conn, opts=OPTS)
41
45
  statement(conn) do |stmt|
@@ -52,6 +56,17 @@ module Sequel
52
56
 
53
57
  private
54
58
 
59
+ # JDBC SQLAnywhere driver does not appear to handle fractional
60
+ # times correctly.
61
+ def default_time_format
62
+ "'%H:%M:%S'"
63
+ end
64
+
65
+ # Set to zero to work around JDBC SQLAnywhere driver bug.
66
+ def sqltime_precision
67
+ 0
68
+ end
69
+
55
70
  SMALLINT_TYPE = Java::JavaSQL::Types::SMALLINT
56
71
  BOOLEAN_METHOD = Object.new
57
72
  def BOOLEAN_METHOD.call(r, i)
@@ -79,6 +79,10 @@ module Sequel
79
79
  super.with_extend(MetadataDatasetMethods)
80
80
  end
81
81
 
82
+ def database_exception_use_sqlstates?
83
+ false
84
+ end
85
+
82
86
  def disconnect_error?(exception, opts)
83
87
  super || (exception.message =~ /connection is closed/)
84
88
  end
@@ -396,11 +396,16 @@ module Sequel
396
396
 
397
397
  def database_exception_sqlstate(exception, opts)
398
398
  if database_exception_use_sqlstates?
399
- while exception.respond_to?(:cause)
400
- exception = exception.cause
401
- return exception.getSQLState if exception.respond_to?(:getSQLState)
402
- end
399
+ _database_exception_sqlstate(exception, opts)
403
400
  end
401
+ end
402
+
403
+ def _database_exception_sqlstate(exception, opts)
404
+ 16.times do
405
+ return exception.getSQLState if exception.respond_to?(:getSQLState)
406
+ break unless exception.respond_to?(:cause) && (exception = exception.cause)
407
+ end
408
+
404
409
  nil
405
410
  end
406
411
 
@@ -415,8 +420,7 @@ module Sequel
415
420
 
416
421
  # Raise a disconnect error if the SQL state of the cause of the exception indicates so.
417
422
  def disconnect_error?(exception, opts)
418
- cause = exception.respond_to?(:cause) ? exception.cause : exception
419
- super || (cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/)
423
+ super || (_database_exception_sqlstate(exception, opts) =~ /^08/)
420
424
  end
421
425
 
422
426
  # Execute the prepared statement. If the provided name is a
@@ -182,8 +182,8 @@ module Sequel
182
182
  1
183
183
  when false
184
184
  0
185
- when DateTime, Time
186
- literal(arg)[1...-1]
185
+ when Time, Date
186
+ @default_dataset.literal_date_or_time(arg, true)
187
187
  else
188
188
  arg
189
189
  end
@@ -43,7 +43,7 @@ module Sequel
43
43
  # Use ODBC format, not Microsoft format, as the ODBC layer does
44
44
  # some translation, but allow for millisecond precision.
45
45
  def default_timestamp_format
46
- "{ts '%Y-%m-%d %H:%M:%S%N'}"
46
+ "{ts '%Y-%m-%d %H:%M:%S.%3N'}"
47
47
  end
48
48
 
49
49
  # Use ODBC format, not Microsoft format, as the ODBC layer does
@@ -188,8 +188,8 @@ module Sequel
188
188
  # :nocov:
189
189
  # Not covered by tests as tests use pg_extended_date_support
190
190
  # extension, which has basically the same code.
191
- when DateTime, Time
192
- literal(arg)
191
+ when Time, DateTime
192
+ @default_dataset.literal_date_or_time(arg)
193
193
  # :nocov:
194
194
  else
195
195
  arg
@@ -672,6 +672,7 @@ module Sequel
672
672
  # cursor usage.
673
673
  # :rows_per_fetch :: The number of rows per fetch (default 1000). Higher
674
674
  # numbers result in fewer queries but greater memory use.
675
+ # :skip_transaction :: Same as :hold, but :hold takes priority.
675
676
  #
676
677
  # Usage:
677
678
  #
@@ -764,13 +765,13 @@ module Sequel
764
765
 
765
766
  # Use a cursor to fetch groups of records at a time, yielding them to the block.
766
767
  def cursor_fetch_rows(sql)
767
- server_opts = {:server=>@opts[:server] || :read_only}
768
768
  cursor = @opts[:cursor]
769
- hold = cursor[:hold]
769
+ hold = cursor.fetch(:hold){cursor[:skip_transaction]}
770
+ server_opts = {:server=>@opts[:server] || :read_only, :skip_transaction=>hold}
770
771
  cursor_name = quote_identifier(cursor[:cursor_name] || 'sequel_cursor')
771
772
  rows_per_fetch = cursor[:rows_per_fetch].to_i
772
773
 
773
- db.public_send(*(hold ? [:synchronize, server_opts[:server]] : [:transaction, server_opts])) do
774
+ db.transaction(server_opts) do
774
775
  begin
775
776
  execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR WITH#{'OUT' unless hold} HOLD FOR #{sql}", server_opts)
776
777
  rows_per_fetch = 1000 if rows_per_fetch <= 0
@@ -215,6 +215,18 @@ module Sequel
215
215
  DATABASE_ERROR_REGEXPS
216
216
  end
217
217
 
218
+ DISCONNECT_SQL_STATES = %w'40003 08001 08003'.freeze
219
+ def disconnect_error?(exception, opts)
220
+ sqlstate = database_exception_sqlstate(exception, opts)
221
+
222
+ case sqlstate
223
+ when *DISCONNECT_SQL_STATES
224
+ true
225
+ else
226
+ super
227
+ end
228
+ end
229
+
218
230
  # DB2 has issues with quoted identifiers, so
219
231
  # turn off database quoting by default.
220
232
  def quote_identifiers_default
@@ -32,7 +32,7 @@ module Sequel
32
32
  #
33
33
  # Options:
34
34
  # :args :: Arguments to stored procedure. For named arguments, this should be a
35
- # hash keyed by argument named. For unnamed arguments, this should be an
35
+ # hash keyed by argument name. For unnamed arguments, this should be an
36
36
  # array. Output parameters to the function are specified using :output.
37
37
  # You can also name output parameters and provide a type by using an
38
38
  # array containing :output, the type name, and the parameter name.
@@ -246,6 +246,34 @@ module Sequel
246
246
  information_schema_tables('VIEW', opts)
247
247
  end
248
248
 
249
+ # Attempt to acquire an exclusive advisory lock with the given lock_id (which will
250
+ # be converted to a string). If successful, yield to the block, then release the advisory lock
251
+ # when the block exits. If unsuccessful, raise a Sequel::AdvisoryLockError.
252
+ #
253
+ # Options:
254
+ # :wait :: Do not raise an error, instead, wait until the advisory lock can be acquired.
255
+ def with_advisory_lock(lock_id, opts=OPTS)
256
+ lock_id = lock_id.to_s
257
+ timeout = opts[:wait] ? -1 : 0
258
+ server = opts[:server]
259
+
260
+ synchronize(server) do
261
+ begin
262
+ res = call_mssql_sproc(:sp_getapplock, :server=>server, :args=>{'Resource'=>lock_id, 'LockTimeout'=>timeout, 'LockMode'=>'Exclusive', 'LockOwner'=>'Session'})
263
+
264
+ unless locked = res[:result] >= 0
265
+ raise AdvisoryLockError, "unable to acquire advisory lock #{lock_id.inspect}"
266
+ end
267
+
268
+ yield
269
+ ensure
270
+ if locked
271
+ call_mssql_sproc(:sp_releaseapplock, :server=>server, :args=>{'Resource'=>lock_id, 'LockOwner'=>'Session'})
272
+ end
273
+ end
274
+ end
275
+ end
276
+
249
277
  private
250
278
 
251
279
  # Add CLUSTERED or NONCLUSTERED as needed
@@ -932,7 +960,7 @@ module Sequel
932
960
  # since that is the format that is multilanguage and not
933
961
  # DATEFORMAT dependent.
934
962
  def default_timestamp_format
935
- "'%Y-%m-%dT%H:%M:%S%N%z'"
963
+ "'%Y-%m-%dT%H:%M:%S.%3N'"
936
964
  end
937
965
 
938
966
  # Only include the primary table in the main delete clause
@@ -197,6 +197,41 @@ module Sequel
197
197
  renames.each{|from,| remove_cached_schema(from)}
198
198
  end
199
199
 
200
+ # Attempt to acquire an exclusive advisory lock with the given lock_id (which will be
201
+ # converted to a string). If successful, yield to the block, then release the advisory lock
202
+ # when the block exits. If unsuccessful, raise a Sequel::AdvisoryLockError.
203
+ #
204
+ # DB.with_advisory_lock(1357){DB.get(1)}
205
+ # # SELECT GET_LOCK('1357', 0) LIMIT 1
206
+ # # SELECT 1 AS v LIMIT 1
207
+ # # SELECT RELEASE_LOCK('1357') LIMIT 1
208
+ #
209
+ # Options:
210
+ # :wait :: Do not raise an error, instead, wait until the advisory lock can be acquired.
211
+ def with_advisory_lock(lock_id, opts=OPTS)
212
+ lock_id = lock_id.to_s
213
+ ds = dataset
214
+ if server = opts[:server]
215
+ ds = ds.server(server)
216
+ end
217
+
218
+ # MariaDB doesn't support negative values for infinite wait. A wait of 34 years
219
+ # should be reasonably similar to infinity for this case.
220
+ timeout = opts[:wait] ? 1073741823 : 0
221
+
222
+ synchronize(server) do |c|
223
+ begin
224
+ unless locked = ds.get{GET_LOCK(lock_id, timeout)} == 1
225
+ raise AdvisoryLockError, "unable to acquire advisory lock #{lock_id.inspect}"
226
+ end
227
+
228
+ yield
229
+ ensure
230
+ ds.get{RELEASE_LOCK(lock_id)} if locked
231
+ end
232
+ end
233
+ end
234
+
200
235
  private
201
236
 
202
237
  def alter_table_add_column_sql(table, op)
@@ -646,7 +681,7 @@ module Sequel
646
681
  MATCH_AGAINST_BOOLEAN = ["MATCH ".freeze, " AGAINST (".freeze, " IN BOOLEAN MODE)".freeze].freeze
647
682
 
648
683
  Dataset.def_sql_method(self, :delete, %w'with delete from where order limit')
649
- Dataset.def_sql_method(self, :insert, %w'insert ignore into columns values on_duplicate_key_update')
684
+ Dataset.def_sql_method(self, :insert, %w'insert ignore into columns values on_duplicate_key_update returning')
650
685
  Dataset.def_sql_method(self, :select, %w'with select distinct calc_found_rows columns from join where group having window compounds order limit lock')
651
686
  Dataset.def_sql_method(self, :update, %w'with update ignore table set where order limit')
652
687
 
@@ -774,6 +809,21 @@ module Sequel
774
809
  clone(:insert_ignore=>true)
775
810
  end
776
811
 
812
+ # Support insert select for associations, so that the model code can use
813
+ # returning instead of a separate query.
814
+ def insert_select(*values)
815
+ return unless supports_insert_select?
816
+ # Handle case where query does not return a row
817
+ server?(:default).with_sql_first(insert_select_sql(*values)) || false
818
+ end
819
+
820
+ # The SQL to use for an insert_select, adds a RETURNING clause to the insert
821
+ # unless the RETURNING clause is already present.
822
+ def insert_select_sql(*values)
823
+ ds = opts[:returning] ? self : returning
824
+ ds.insert_sql(*values)
825
+ end
826
+
777
827
  # Sets up the insert methods to use ON DUPLICATE KEY UPDATE
778
828
  # If you pass no arguments, ALL fields will be
779
829
  # updated with the new values. If you pass the fields you
@@ -871,9 +921,14 @@ module Sequel
871
921
  true
872
922
  end
873
923
 
874
- # MySQL 8+ supports SKIP LOCKED.
924
+ # MariaDB 10.5.0 supports INSERT RETURNING.
925
+ def supports_returning?(type)
926
+ (type == :insert && db.mariadb? && db.adapter_scheme != :jdbc) ? (db.server_version >= 100500) : false
927
+ end
928
+
929
+ # MySQL 8+ and MariaDB 10.6+ support SKIP LOCKED.
875
930
  def supports_skip_locked?
876
- !db.mariadb? && db.server_version >= 80000
931
+ db.server_version >= (db.mariadb? ? 100600 : 80000)
877
932
  end
878
933
 
879
934
  # Check the database setting for whether fractional timestamps
@@ -909,6 +964,16 @@ module Sequel
909
964
  super if type == :truncate || @opts[:offset]
910
965
  end
911
966
 
967
+ # The strftime format to use when literalizing time (Sequel::SQLTime) values.
968
+ def default_time_format
969
+ db.supports_timestamp_usecs? ? super : "'%H:%M:%S'"
970
+ end
971
+
972
+ # The strftime format to use when literalizing timestamp (Time/DateTime) values.
973
+ def default_timestamp_format
974
+ db.supports_timestamp_usecs? ? super : "'%Y-%m-%d %H:%M:%S'"
975
+ end
976
+
912
977
  # Consider the first table in the joined dataset is the table to delete
913
978
  # from, but include the others for the purposes of selecting rows.
914
979
  def delete_from_sql(sql)
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require_relative '../utils/emulate_offset_with_row_number'
4
+ require_relative '../../extensions/auto_cast_date_and_time'
4
5
 
5
6
  module Sequel
6
7
  module Oracle
@@ -326,6 +327,8 @@ module Sequel
326
327
  end
327
328
 
328
329
  module DatasetMethods
330
+ include AutoCastDateAndTime
331
+
329
332
  ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
330
333
  BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
331
334
 
@@ -623,7 +626,7 @@ module Sequel
623
626
 
624
627
  # The strftime format to use when literalizing the time.
625
628
  def default_timestamp_format
626
- "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'"
629
+ "'%Y-%m-%d %H:%M:%S.%6N %:z'"
627
630
  end
628
631
 
629
632
  def empty_from_sql
@@ -660,11 +663,6 @@ module Sequel
660
663
  super
661
664
  end
662
665
 
663
- # Use a colon for the timestamp offset, since Oracle appears to require it.
664
- def format_timestamp_offset(hour, minute)
665
- sprintf("%+03i:%02i", hour, minute)
666
- end
667
-
668
666
  # Oracle doesn't support empty values when inserting.
669
667
  def insert_supports_empty_values?
670
668
  false