sequel 5.58.0 → 5.78.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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +288 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +24 -23
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +53 -17
  8. data/doc/cheat_sheet.rdoc +3 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +15 -0
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +20 -12
  14. data/doc/postgresql.rdoc +8 -8
  15. data/doc/querying.rdoc +1 -1
  16. data/doc/release_notes/5.59.0.txt +73 -0
  17. data/doc/release_notes/5.60.0.txt +22 -0
  18. data/doc/release_notes/5.61.0.txt +43 -0
  19. data/doc/release_notes/5.62.0.txt +132 -0
  20. data/doc/release_notes/5.63.0.txt +33 -0
  21. data/doc/release_notes/5.64.0.txt +50 -0
  22. data/doc/release_notes/5.65.0.txt +21 -0
  23. data/doc/release_notes/5.66.0.txt +24 -0
  24. data/doc/release_notes/5.67.0.txt +32 -0
  25. data/doc/release_notes/5.68.0.txt +61 -0
  26. data/doc/release_notes/5.69.0.txt +26 -0
  27. data/doc/release_notes/5.70.0.txt +35 -0
  28. data/doc/release_notes/5.71.0.txt +21 -0
  29. data/doc/release_notes/5.72.0.txt +33 -0
  30. data/doc/release_notes/5.73.0.txt +66 -0
  31. data/doc/release_notes/5.74.0.txt +45 -0
  32. data/doc/release_notes/5.75.0.txt +35 -0
  33. data/doc/release_notes/5.76.0.txt +86 -0
  34. data/doc/release_notes/5.77.0.txt +63 -0
  35. data/doc/release_notes/5.78.0.txt +67 -0
  36. data/doc/schema_modification.rdoc +3 -3
  37. data/doc/security.rdoc +9 -9
  38. data/doc/sharding.rdoc +3 -1
  39. data/doc/sql.rdoc +14 -14
  40. data/doc/testing.rdoc +16 -12
  41. data/doc/transactions.rdoc +6 -6
  42. data/doc/virtual_rows.rdoc +1 -1
  43. data/lib/sequel/adapters/ibmdb.rb +1 -1
  44. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  45. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  46. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  48. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  49. data/lib/sequel/adapters/jdbc.rb +10 -6
  50. data/lib/sequel/adapters/mysql.rb +19 -7
  51. data/lib/sequel/adapters/mysql2.rb +2 -2
  52. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  53. data/lib/sequel/adapters/oracle.rb +1 -0
  54. data/lib/sequel/adapters/postgres.rb +62 -16
  55. data/lib/sequel/adapters/shared/access.rb +9 -1
  56. data/lib/sequel/adapters/shared/db2.rb +12 -0
  57. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  58. data/lib/sequel/adapters/shared/mysql.rb +80 -1
  59. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  60. data/lib/sequel/adapters/shared/postgres.rb +494 -164
  61. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  62. data/lib/sequel/adapters/shared/sqlite.rb +40 -4
  63. data/lib/sequel/adapters/sqlite.rb +42 -3
  64. data/lib/sequel/adapters/trilogy.rb +117 -0
  65. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  66. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  67. data/lib/sequel/connection_pool/threaded.rb +14 -8
  68. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  69. data/lib/sequel/connection_pool.rb +57 -31
  70. data/lib/sequel/database/connecting.rb +25 -1
  71. data/lib/sequel/database/dataset.rb +16 -6
  72. data/lib/sequel/database/misc.rb +65 -14
  73. data/lib/sequel/database/query.rb +72 -1
  74. data/lib/sequel/database/schema_generator.rb +2 -1
  75. data/lib/sequel/database/schema_methods.rb +13 -3
  76. data/lib/sequel/database/transactions.rb +6 -0
  77. data/lib/sequel/dataset/actions.rb +60 -13
  78. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  79. data/lib/sequel/dataset/features.rb +15 -1
  80. data/lib/sequel/dataset/misc.rb +12 -2
  81. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  82. data/lib/sequel/dataset/query.rb +62 -37
  83. data/lib/sequel/dataset/sql.rb +58 -36
  84. data/lib/sequel/dataset.rb +4 -0
  85. data/lib/sequel/exceptions.rb +5 -0
  86. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  87. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  88. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  89. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  90. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  91. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  92. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  93. data/lib/sequel/extensions/connection_validator.rb +16 -11
  94. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  95. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  96. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  97. data/lib/sequel/extensions/index_caching.rb +5 -1
  98. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  99. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  100. data/lib/sequel/extensions/migration.rb +65 -15
  101. data/lib/sequel/extensions/named_timezones.rb +22 -6
  102. data/lib/sequel/extensions/pg_array.rb +33 -4
  103. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  104. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  105. data/lib/sequel/extensions/pg_enum.rb +1 -2
  106. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  107. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  108. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  109. data/lib/sequel/extensions/pg_inet.rb +10 -11
  110. data/lib/sequel/extensions/pg_interval.rb +10 -11
  111. data/lib/sequel/extensions/pg_json.rb +10 -10
  112. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  113. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  114. data/lib/sequel/extensions/pg_range.rb +9 -14
  115. data/lib/sequel/extensions/pg_row.rb +20 -19
  116. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  117. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  118. data/lib/sequel/extensions/schema_caching.rb +1 -1
  119. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  120. data/lib/sequel/extensions/server_block.rb +2 -1
  121. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  122. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  123. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  124. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  125. data/lib/sequel/model/associations.rb +50 -11
  126. data/lib/sequel/model/base.rb +45 -21
  127. data/lib/sequel/model/dataset_module.rb +3 -0
  128. data/lib/sequel/model/exceptions.rb +15 -3
  129. data/lib/sequel/plugins/auto_validations.rb +53 -15
  130. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  131. data/lib/sequel/plugins/column_encryption.rb +27 -6
  132. data/lib/sequel/plugins/composition.rb +2 -2
  133. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  134. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  135. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  136. data/lib/sequel/plugins/dirty.rb +1 -1
  137. data/lib/sequel/plugins/finder.rb +4 -2
  138. data/lib/sequel/plugins/list.rb +8 -3
  139. data/lib/sequel/plugins/many_through_many.rb +1 -1
  140. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  141. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  142. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  143. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  144. data/lib/sequel/plugins/paged_operations.rb +181 -0
  145. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  146. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  147. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  148. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  149. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  150. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  151. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  152. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  153. data/lib/sequel/plugins/sql_comments.rb +5 -5
  154. data/lib/sequel/plugins/static_cache.rb +38 -0
  155. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  156. data/lib/sequel/plugins/tactical_eager_loading.rb +21 -14
  157. data/lib/sequel/plugins/validate_associated.rb +22 -12
  158. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  159. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  160. data/lib/sequel/version.rb +1 -1
  161. metadata +76 -6
@@ -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
@@ -29,6 +29,21 @@ module Sequel
29
29
  end
30
30
  MYSQL_TYPES.freeze
31
31
 
32
+ RUBY_MYSQL_3 = !Mysql.respond_to?(:init)
33
+ RUBY_MYSQL_4 = RUBY_MYSQL_3 && ::Mysql::VERSION.to_i >= 4
34
+
35
+ if RUBY_MYSQL_3
36
+ class Adapter < ::Mysql
37
+ alias real_connect connect
38
+ alias use_result store_result
39
+ if RUBY_MYSQL_4
40
+ def initialize(**opts)
41
+ super(**opts.merge(:cast=>false))
42
+ end
43
+ end
44
+ end
45
+ end
46
+
32
47
  class Database < Sequel::Database
33
48
  include Sequel::MySQL::DatabaseMethods
34
49
  include Sequel::MySQL::MysqlMysql2::DatabaseMethods
@@ -72,7 +87,7 @@ module Sequel
72
87
  def connect(server)
73
88
  opts = server_opts(server)
74
89
 
75
- if Mysql.respond_to?(:init)
90
+ if !RUBY_MYSQL_3
76
91
  conn = Mysql.init
77
92
  conn.options(Mysql::READ_DEFAULT_GROUP, opts[:config_default_group] || "client")
78
93
  conn.options(Mysql::OPT_LOCAL_INFILE, opts[:config_local_infile]) if opts.has_key?(:config_local_infile)
@@ -88,8 +103,8 @@ module Sequel
88
103
  conn.options(Mysql::OPT_CONNECT_TIMEOUT, connect_timeout)
89
104
  end
90
105
  else
91
- # ruby-mysql 3 API
92
- conn = Mysql.new
106
+ # ruby-mysql 3+ API
107
+ conn = Adapter.new
93
108
  # no support for default group
94
109
  conn.local_infile = opts[:config_local_infile] if opts.has_key?(:config_local_infile)
95
110
  if encoding = opts[:encoding] || opts[:charset]
@@ -101,10 +116,7 @@ module Sequel
101
116
  if connect_timeout = opts[:connect_timeout]
102
117
  conn.connect_timeout = connect_timeout
103
118
  end
104
- conn.singleton_class.class_eval do
105
- alias real_connect connect
106
- alias use_result store_result
107
- end
119
+ opts[:compress] = false
108
120
  end
109
121
 
110
122
  conn.ssl_set(opts[:sslkey], opts[:sslcert], opts[:sslca], opts[:sslcapath], opts[:sslcipher]) if opts[:sslca] || opts[:sslkey]
@@ -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
@@ -312,6 +312,7 @@ module Sequel
312
312
  :char_used => column.char_used?,
313
313
  :char_size => column.char_size,
314
314
  :data_size => column.data_size,
315
+ :column_size => column.precision,
315
316
  :precision => column.precision,
316
317
  :scale => column.scale,
317
318
  :fsprecision => column.fsprecision,
@@ -5,6 +5,7 @@ require_relative 'shared/postgres'
5
5
  begin
6
6
  require 'pg'
7
7
 
8
+ # :nocov:
8
9
  Sequel::Postgres::PGError = PG::Error if defined?(PG::Error)
9
10
  Sequel::Postgres::PGconn = PG::Connection if defined?(PG::Connection)
10
11
  Sequel::Postgres::PGresult = PG::Result if defined?(PG::Result)
@@ -14,30 +15,40 @@ begin
14
15
  raise LoadError unless defined?(PGconn::CONNECTION_OK)
15
16
  end
16
17
 
17
- Sequel::Postgres::USES_PG = true
18
18
  if defined?(PG::TypeMapByClass)
19
+ # :nocov:
19
20
  type_map = Sequel::Postgres::PG_QUERY_TYPE_MAP = PG::TypeMapByClass.new
20
21
  type_map[Integer] = PG::TextEncoder::Integer.new
21
22
  type_map[FalseClass] = type_map[TrueClass] = PG::TextEncoder::Boolean.new
22
23
  type_map[Float] = PG::TextEncoder::Float.new
23
24
  end
25
+
26
+ Sequel::Postgres::USES_PG = true
24
27
  rescue LoadError => e
28
+ # :nocov:
25
29
  begin
26
- require 'postgres-pr/postgres-compat'
27
- Sequel::Postgres::USES_PG = false
30
+ require 'sequel/postgres-pr'
28
31
  rescue LoadError
29
- raise e
32
+ begin
33
+ require 'postgres-pr/postgres-compat'
34
+ rescue LoadError
35
+ raise e
36
+ end
30
37
  end
38
+ Sequel::Postgres::USES_PG = false
39
+ # :nocov:
31
40
  end
32
41
 
33
42
  module Sequel
34
43
  module Postgres
35
- if Sequel::Postgres::USES_PG
44
+ # :nocov:
45
+ if USES_PG
36
46
  # Whether the given sequel_pg version integer is supported.
37
47
  def self.sequel_pg_version_supported?(version)
38
48
  version >= 10617
39
49
  end
40
50
  end
51
+ # :nocov:
41
52
 
42
53
  # PGconn subclass for connection specific methods used with the
43
54
  # pg or postgres-pr driver.
@@ -45,7 +56,9 @@ module Sequel
45
56
  # The underlying exception classes to reraise as disconnect errors
46
57
  # instead of regular database errors.
47
58
  DISCONNECT_ERROR_CLASSES = [IOError, Errno::EPIPE, Errno::ECONNRESET]
59
+ # :nocov:
48
60
  if defined?(::PG::ConnectionBad)
61
+ # :nocov:
49
62
  DISCONNECT_ERROR_CLASSES << ::PG::ConnectionBad
50
63
  end
51
64
  DISCONNECT_ERROR_CLASSES.freeze
@@ -71,11 +84,14 @@ module Sequel
71
84
  # are SQL strings.
72
85
  attr_reader :prepared_statements
73
86
 
87
+ # :nocov:
74
88
  unless public_method_defined?(:async_exec_params)
75
89
  alias async_exec_params async_exec
76
90
  end
77
- else
78
- # Make postgres-pr look like pg
91
+ elsif !const_defined?(:CONNECTION_OK)
92
+ # Handle old postgres-pr
93
+ # sequel-postgres-pr already implements this API
94
+
79
95
  CONNECTION_OK = -1
80
96
 
81
97
  # Escape bytea values. Uses historical format instead of hex
@@ -111,6 +127,7 @@ module Sequel
111
127
  alias cmd_tuples cmdtuples
112
128
  end
113
129
  end
130
+ # :nocov:
114
131
 
115
132
  # Raise a Sequel::DatabaseDisconnectError if a one of the disconnect
116
133
  # error classes is raised, or a PGError is raised and the connection
@@ -168,8 +185,12 @@ module Sequel
168
185
  case arg
169
186
  when Sequel::SQL::Blob
170
187
  {:value=>arg, :type=>17, :format=>1}
171
- when DateTime, Time
172
- literal(arg)
188
+ # :nocov:
189
+ # Not covered by tests as tests use pg_extended_date_support
190
+ # extension, which has basically the same code.
191
+ when Time, DateTime
192
+ @default_dataset.literal_date_or_time(arg)
193
+ # :nocov:
173
194
  else
174
195
  arg
175
196
  end
@@ -204,7 +225,9 @@ module Sequel
204
225
  :sslmode => opts[:sslmode],
205
226
  :sslrootcert => opts[:sslrootcert]
206
227
  }.delete_if { |key, value| blank_object?(value) }
228
+ # :nocov:
207
229
  connection_params.merge!(opts[:driver_options]) if opts[:driver_options]
230
+ # :nocov:
208
231
  conn = Adapter.connect(opts[:conn_str] || connection_params)
209
232
 
210
233
  conn.instance_variable_set(:@prepared_statements, {})
@@ -212,6 +235,13 @@ module Sequel
212
235
  if receiver = opts[:notice_receiver]
213
236
  conn.set_notice_receiver(&receiver)
214
237
  end
238
+
239
+ # :nocov:
240
+ if conn.respond_to?(:type_map_for_queries=) && defined?(PG_QUERY_TYPE_MAP)
241
+ # :nocov:
242
+ conn.type_map_for_queries = PG_QUERY_TYPE_MAP
243
+ end
244
+ # :nocov:
215
245
  else
216
246
  unless typecast_value_boolean(@opts.fetch(:force_standard_strings, true))
217
247
  raise Error, "Cannot create connection using postgres-pr unless force_standard_strings is set"
@@ -226,12 +256,11 @@ module Sequel
226
256
  opts[:password]
227
257
  )
228
258
  end
259
+ # :nocov:
229
260
 
230
261
  conn.instance_variable_set(:@db, self)
231
- if USES_PG && conn.respond_to?(:type_map_for_queries=) && defined?(PG_QUERY_TYPE_MAP)
232
- conn.type_map_for_queries = PG_QUERY_TYPE_MAP
233
- end
234
262
 
263
+ # :nocov:
235
264
  if encoding = opts[:encoding] || opts[:charset]
236
265
  if conn.respond_to?(:set_client_encoding)
237
266
  conn.set_client_encoding(encoding)
@@ -239,6 +268,7 @@ module Sequel
239
268
  conn.async_exec("set client_encoding to '#{encoding}'")
240
269
  end
241
270
  end
271
+ # :nocov:
242
272
 
243
273
  connection_configuration_sqls(opts).each{|sql| conn.execute(sql)}
244
274
  conn
@@ -265,7 +295,9 @@ module Sequel
265
295
  nil
266
296
  end
267
297
 
298
+ # :nocov:
268
299
  if USES_PG && Object.const_defined?(:PG) && ::PG.const_defined?(:Constants) && ::PG::Constants.const_defined?(:PG_DIAG_SCHEMA_NAME)
300
+ # :nocov:
269
301
  # Return a hash of information about the related PGError (or Sequel::DatabaseError that
270
302
  # wraps a PGError), with the following entries (any of which may be +nil+):
271
303
  #
@@ -316,7 +348,9 @@ module Sequel
316
348
  synchronize(opts[:server]){|conn| check_database_errors{_execute(conn, sql, opts, &block)}}
317
349
  end
318
350
 
351
+ # :nocov:
319
352
  if USES_PG
353
+ # :nocov:
320
354
  # +copy_table+ uses PostgreSQL's +COPY TO STDOUT+ SQL statement to return formatted
321
355
  # results directly to the caller. This method is only supported if pg is the
322
356
  # underlying ruby driver. This method should only be called if you want
@@ -509,8 +543,10 @@ module Sequel
509
543
  def adapter_initialize
510
544
  @use_iso_date_format = typecast_value_boolean(@opts.fetch(:use_iso_date_format, true))
511
545
  initialize_postgres_adapter
546
+ # :nocov:
512
547
  add_conversion_proc(17, method(:unescape_bytea)) if USES_PG
513
548
  add_conversion_proc(1082, TYPE_TRANSLATOR_DATE) if @use_iso_date_format
549
+ # :nocov:
514
550
  self.convert_infinite_timestamps = @opts[:convert_infinite_timestamps]
515
551
  end
516
552
 
@@ -520,19 +556,22 @@ module Sequel
520
556
  rescue => e
521
557
  raise_error(e, :classes=>database_error_classes)
522
558
  end
523
-
524
559
  # Set the DateStyle to ISO if configured, for faster date parsing.
525
560
  def connection_configuration_sqls(opts=@opts)
526
561
  sqls = super
562
+ # :nocov:
527
563
  sqls << "SET DateStyle = 'ISO'" if @use_iso_date_format
564
+ # :nocov:
528
565
  sqls
529
566
  end
530
567
 
568
+ # :nocov:
531
569
  if USES_PG
532
570
  def unescape_bytea(s)
533
571
  ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))
534
572
  end
535
573
  end
574
+ # :nocov:
536
575
 
537
576
  DATABASE_ERROR_CLASSES = [PGError].freeze
538
577
  def database_error_classes
@@ -546,7 +585,9 @@ module Sequel
546
585
  end
547
586
 
548
587
  def database_exception_sqlstate(exception, opts)
588
+ # :nocov:
549
589
  if exception.respond_to?(:result) && (result = exception.result)
590
+ # :nocov:
550
591
  result.error_field(PGresult::PG_DIAG_SQLSTATE)
551
592
  end
552
593
  end
@@ -631,6 +672,7 @@ module Sequel
631
672
  # cursor usage.
632
673
  # :rows_per_fetch :: The number of rows per fetch (default 1000). Higher
633
674
  # numbers result in fewer queries but greater memory use.
675
+ # :skip_transaction :: Same as :hold, but :hold takes priority.
634
676
  #
635
677
  # Usage:
636
678
  #
@@ -656,7 +698,9 @@ module Sequel
656
698
  clone(:where=>Sequel.lit(['CURRENT OF '], Sequel.identifier(cursor_name)))
657
699
  end
658
700
 
701
+ # :nocov:
659
702
  if USES_PG
703
+ # :nocov:
660
704
  PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
661
705
 
662
706
  # PostgreSQL specific argument mapper used for mapping the named
@@ -721,13 +765,13 @@ module Sequel
721
765
 
722
766
  # Use a cursor to fetch groups of records at a time, yielding them to the block.
723
767
  def cursor_fetch_rows(sql)
724
- server_opts = {:server=>@opts[:server] || :read_only}
725
768
  cursor = @opts[:cursor]
726
- hold = cursor[:hold]
769
+ hold = cursor.fetch(:hold){cursor[:skip_transaction]}
770
+ server_opts = {:server=>@opts[:server] || :read_only, :skip_transaction=>hold}
727
771
  cursor_name = quote_identifier(cursor[:cursor_name] || 'sequel_cursor')
728
772
  rows_per_fetch = cursor[:rows_per_fetch].to_i
729
773
 
730
- db.public_send(*(hold ? [:synchronize, server_opts[:server]] : [:transaction, server_opts])) do
774
+ db.transaction(server_opts) do
731
775
  begin
732
776
  execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR WITH#{'OUT' unless hold} HOLD FOR #{sql}", server_opts)
733
777
  rows_per_fetch = 1000 if rows_per_fetch <= 0
@@ -803,6 +847,7 @@ module Sequel
803
847
  end
804
848
  end
805
849
 
850
+ # :nocov:
806
851
  if Sequel::Postgres::USES_PG && !ENV['NO_SEQUEL_PG']
807
852
  begin
808
853
  require 'sequel_pg'
@@ -814,3 +859,4 @@ if Sequel::Postgres::USES_PG && !ENV['NO_SEQUEL_PG']
814
859
  rescue LoadError
815
860
  end
816
861
  end
862
+ # :nocov:
@@ -17,7 +17,7 @@ module Sequel
17
17
 
18
18
  # Doesn't work, due to security restrictions on MSysObjects
19
19
  #def tables
20
- # from(:MSysObjects).where(:Type=>1, :Flags=>0).select_map(:Name).map(&:to_sym)
20
+ # from(:MSysObjects).where(Type: 1, Flags: 0).select_map(:Name).map(&:to_sym)
21
21
  #end
22
22
 
23
23
  # Access doesn't support renaming tables from an SQL query,
@@ -57,6 +57,14 @@ module Sequel
57
57
  DATABASE_ERROR_REGEXPS
58
58
  end
59
59
 
60
+ # Access's Byte type will accept much larger values,
61
+ # even though it only stores 0-255. Do not set min/max
62
+ # values for the Byte type.
63
+ def column_schema_integer_min_max_values(column)
64
+ return if /byte/i =~ column[:db_type]
65
+ super
66
+ end
67
+
60
68
  def drop_index_sql(table, op)
61
69
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
62
70
  end
@@ -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
@@ -325,6 +353,11 @@ module Sequel
325
353
  false
326
354
  end
327
355
 
356
+ # MSSQL tinyint types are unsigned.
357
+ def column_schema_tinyint_type_is_unsigned?
358
+ true
359
+ end
360
+
328
361
  # Handle MSSQL specific default format.
329
362
  def column_schema_normalize_default(default, type)
330
363
  if m = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/.match(default)
@@ -399,10 +432,15 @@ module Sequel
399
432
  # Backbone of the tables and views support.
400
433
  def information_schema_tables(type, opts)
401
434
  m = output_identifier_meth
402
- metadata_dataset.from(Sequel[:information_schema][:tables].as(:t)).
435
+ schema = opts[:schema]||'dbo'
436
+ tables = metadata_dataset.from(Sequel[:information_schema][:tables].as(:t)).
403
437
  select(:table_name).
404
- where(:table_type=>type, :table_schema=>(opts[:schema]||'dbo').to_s).
438
+ where(:table_type=>type, :table_schema=>schema.to_s).
405
439
  map{|x| m.call(x[:table_name])}
440
+
441
+ tables.map!{|t| Sequel.qualify(m.call(schema).to_s, m.call(t).to_s)} if opts[:qualify]
442
+
443
+ tables
406
444
  end
407
445
 
408
446
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
@@ -586,6 +624,18 @@ module Sequel
586
624
  end
587
625
  end
588
626
 
627
+ # For a dataset with custom SQL, since it may include ORDER BY, you
628
+ # cannot wrap it in a subquery. Load entire query in this case to get
629
+ # the number of rows. In general, you should avoid calling this method
630
+ # on datasets with custom SQL.
631
+ def count(*a, &block)
632
+ if (@opts[:sql] && a.empty? && !block)
633
+ naked.to_a.length
634
+ else
635
+ super
636
+ end
637
+ end
638
+
589
639
  # Uses CROSS APPLY to join the given table into the current dataset.
590
640
  def cross_apply(table)
591
641
  join_table(:cross_apply, table)
@@ -596,6 +646,19 @@ module Sequel
596
646
  clone(:disable_insert_output=>true)
597
647
  end
598
648
 
649
+ # For a dataset with custom SQL, since it may include ORDER BY, you
650
+ # cannot wrap it in a subquery. Run query, and if it returns any
651
+ # records, return true. In general, you should avoid calling this method
652
+ # on datasets with custom SQL.
653
+ def empty?
654
+ if @opts[:sql]
655
+ naked.each{return false}
656
+ true
657
+ else
658
+ super
659
+ end
660
+ end
661
+
599
662
  # MSSQL treats [] as a metacharacter in LIKE expresions.
600
663
  def escape_like(string)
601
664
  string.gsub(/[\\%_\[\]]/){|m| "\\#{m}"}
@@ -800,11 +863,10 @@ module Sequel
800
863
  if opts[:return] == :primary_key && !@opts[:output]
801
864
  output(nil, [SQL::QualifiedIdentifier.new(:inserted, first_primary_key)])._import(columns, values, opts)
802
865
  elsif @opts[:output]
803
- statements = multi_insert_sql(columns, values)
804
- ds = naked
805
- @db.transaction(opts.merge(:server=>@opts[:server])) do
806
- statements.map{|st| ds.with_sql(st)}
807
- end.first.map{|v| v.length == 1 ? v.values.first : v}
866
+ # no transaction: our multi_insert_sql_strategy should guarantee
867
+ # that there's only ever a single statement.
868
+ sql = multi_insert_sql(columns, values)[0]
869
+ naked.with_sql(sql).map{|v| v.length == 1 ? v.values.first : v}
808
870
  else
809
871
  super
810
872
  end
@@ -898,7 +960,7 @@ module Sequel
898
960
  # since that is the format that is multilanguage and not
899
961
  # DATEFORMAT dependent.
900
962
  def default_timestamp_format
901
- "'%Y-%m-%dT%H:%M:%S%N%z'"
963
+ "'%Y-%m-%dT%H:%M:%S.%3N'"
902
964
  end
903
965
 
904
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)
@@ -550,6 +585,20 @@ module Sequel
550
585
  end
551
586
  end
552
587
 
588
+ # Return nil if CHECK constraints are not supported, because
589
+ # versions that don't support check constraints don't raise
590
+ # errors for values outside of range.
591
+ def column_schema_integer_min_max_values(column)
592
+ super if supports_check_constraints?
593
+ end
594
+
595
+ # Return nil if CHECK constraints are not supported, because
596
+ # versions that don't support check constraints don't raise
597
+ # errors for values outside of range.
598
+ def column_schema_decimal_min_max_values(column)
599
+ super if supports_check_constraints?
600
+ end
601
+
553
602
  # Split DROP INDEX ops on MySQL 5.6+, as dropping them in the same
554
603
  # statement as dropping a related foreign key causes an error.
555
604
  def split_alter_table_op?(op)
@@ -632,7 +681,7 @@ module Sequel
632
681
  MATCH_AGAINST_BOOLEAN = ["MATCH ".freeze, " AGAINST (".freeze, " IN BOOLEAN MODE)".freeze].freeze
633
682
 
634
683
  Dataset.def_sql_method(self, :delete, %w'with delete from where order limit')
635
- 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')
636
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')
637
686
  Dataset.def_sql_method(self, :update, %w'with update ignore table set where order limit')
638
687
 
@@ -760,6 +809,21 @@ module Sequel
760
809
  clone(:insert_ignore=>true)
761
810
  end
762
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
+
763
827
  # Sets up the insert methods to use ON DUPLICATE KEY UPDATE
764
828
  # If you pass no arguments, ALL fields will be
765
829
  # updated with the new values. If you pass the fields you
@@ -857,6 +921,11 @@ module Sequel
857
921
  true
858
922
  end
859
923
 
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
+
860
929
  # MySQL 8+ supports SKIP LOCKED.
861
930
  def supports_skip_locked?
862
931
  !db.mariadb? && db.server_version >= 80000
@@ -895,6 +964,16 @@ module Sequel
895
964
  super if type == :truncate || @opts[:offset]
896
965
  end
897
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
+
898
977
  # Consider the first table in the joined dataset is the table to delete
899
978
  # from, but include the others for the purposes of selecting rows.
900
979
  def delete_from_sql(sql)