sequel 5.58.0 → 5.78.0

Sign up to get free protection for your applications and to get access to all the features.
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)