sequel 5.60.1 → 5.81.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +280 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +27 -24
  5. data/doc/advanced_associations.rdoc +16 -14
  6. data/doc/association_basics.rdoc +53 -17
  7. data/doc/cheat_sheet.rdoc +3 -3
  8. data/doc/code_order.rdoc +5 -3
  9. data/doc/dataset_basics.rdoc +1 -1
  10. data/doc/mass_assignment.rdoc +1 -1
  11. data/doc/migration.rdoc +15 -0
  12. data/doc/model_hooks.rdoc +1 -1
  13. data/doc/object_model.rdoc +8 -8
  14. data/doc/opening_databases.rdoc +18 -7
  15. data/doc/postgresql.rdoc +8 -8
  16. data/doc/querying.rdoc +7 -2
  17. data/doc/release_notes/5.61.0.txt +43 -0
  18. data/doc/release_notes/5.62.0.txt +132 -0
  19. data/doc/release_notes/5.63.0.txt +33 -0
  20. data/doc/release_notes/5.64.0.txt +50 -0
  21. data/doc/release_notes/5.65.0.txt +21 -0
  22. data/doc/release_notes/5.66.0.txt +24 -0
  23. data/doc/release_notes/5.67.0.txt +32 -0
  24. data/doc/release_notes/5.68.0.txt +61 -0
  25. data/doc/release_notes/5.69.0.txt +26 -0
  26. data/doc/release_notes/5.70.0.txt +35 -0
  27. data/doc/release_notes/5.71.0.txt +21 -0
  28. data/doc/release_notes/5.72.0.txt +33 -0
  29. data/doc/release_notes/5.73.0.txt +66 -0
  30. data/doc/release_notes/5.74.0.txt +45 -0
  31. data/doc/release_notes/5.75.0.txt +35 -0
  32. data/doc/release_notes/5.76.0.txt +86 -0
  33. data/doc/release_notes/5.77.0.txt +63 -0
  34. data/doc/release_notes/5.78.0.txt +67 -0
  35. data/doc/release_notes/5.79.0.txt +28 -0
  36. data/doc/release_notes/5.80.0.txt +40 -0
  37. data/doc/release_notes/5.81.0.txt +31 -0
  38. data/doc/schema_modification.rdoc +3 -3
  39. data/doc/security.rdoc +9 -9
  40. data/doc/sharding.rdoc +3 -1
  41. data/doc/sql.rdoc +14 -14
  42. data/doc/testing.rdoc +16 -12
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +1 -1
  45. data/lib/sequel/adapters/ibmdb.rb +1 -1
  46. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  48. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  49. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  50. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  51. data/lib/sequel/adapters/jdbc.rb +10 -6
  52. data/lib/sequel/adapters/mysql.rb +19 -7
  53. data/lib/sequel/adapters/mysql2.rb +2 -2
  54. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  55. data/lib/sequel/adapters/oracle.rb +1 -0
  56. data/lib/sequel/adapters/postgres.rb +10 -5
  57. data/lib/sequel/adapters/shared/access.rb +9 -1
  58. data/lib/sequel/adapters/shared/db2.rb +12 -0
  59. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  60. data/lib/sequel/adapters/shared/mysql.rb +82 -3
  61. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  62. data/lib/sequel/adapters/shared/postgres.rb +469 -160
  63. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  64. data/lib/sequel/adapters/shared/sqlite.rb +39 -3
  65. data/lib/sequel/adapters/sqlite.rb +42 -3
  66. data/lib/sequel/adapters/trilogy.rb +117 -0
  67. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  68. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  69. data/lib/sequel/connection_pool/threaded.rb +14 -8
  70. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  71. data/lib/sequel/connection_pool.rb +57 -31
  72. data/lib/sequel/database/connecting.rb +25 -1
  73. data/lib/sequel/database/dataset.rb +16 -6
  74. data/lib/sequel/database/misc.rb +65 -14
  75. data/lib/sequel/database/query.rb +72 -1
  76. data/lib/sequel/database/schema_generator.rb +1 -1
  77. data/lib/sequel/database/schema_methods.rb +12 -5
  78. data/lib/sequel/database/transactions.rb +6 -0
  79. data/lib/sequel/dataset/actions.rb +60 -13
  80. data/lib/sequel/dataset/dataset_module.rb +1 -1
  81. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  82. data/lib/sequel/dataset/features.rb +15 -1
  83. data/lib/sequel/dataset/graph.rb +1 -0
  84. data/lib/sequel/dataset/misc.rb +12 -2
  85. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  86. data/lib/sequel/dataset/query.rb +119 -45
  87. data/lib/sequel/dataset/sql.rb +58 -36
  88. data/lib/sequel/dataset.rb +4 -0
  89. data/lib/sequel/exceptions.rb +5 -0
  90. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  91. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  92. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  93. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  94. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/caller_logging.rb +4 -1
  97. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  98. data/lib/sequel/extensions/connection_validator.rb +16 -11
  99. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  100. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  101. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  102. data/lib/sequel/extensions/index_caching.rb +5 -1
  103. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  104. data/lib/sequel/extensions/migration.rb +65 -15
  105. data/lib/sequel/extensions/named_timezones.rb +22 -6
  106. data/lib/sequel/extensions/pg_array.rb +33 -4
  107. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  108. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  109. data/lib/sequel/extensions/pg_enum.rb +1 -2
  110. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  111. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  112. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  113. data/lib/sequel/extensions/pg_inet.rb +10 -11
  114. data/lib/sequel/extensions/pg_interval.rb +10 -11
  115. data/lib/sequel/extensions/pg_json.rb +10 -10
  116. data/lib/sequel/extensions/pg_json_ops.rb +3 -3
  117. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  118. data/lib/sequel/extensions/pg_range.rb +9 -14
  119. data/lib/sequel/extensions/pg_row.rb +20 -19
  120. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  121. data/lib/sequel/extensions/provenance.rb +110 -0
  122. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  123. data/lib/sequel/extensions/schema_caching.rb +1 -1
  124. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  125. data/lib/sequel/extensions/server_block.rb +2 -1
  126. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  127. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  128. data/lib/sequel/extensions/temporarily_release_connection.rb +178 -0
  129. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  130. data/lib/sequel/model/associations.rb +38 -11
  131. data/lib/sequel/model/base.rb +32 -18
  132. data/lib/sequel/model/dataset_module.rb +3 -0
  133. data/lib/sequel/model/exceptions.rb +15 -3
  134. data/lib/sequel/plugins/auto_validations.rb +53 -15
  135. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  136. data/lib/sequel/plugins/column_encryption.rb +28 -7
  137. data/lib/sequel/plugins/composition.rb +2 -2
  138. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  139. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  140. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  141. data/lib/sequel/plugins/dirty.rb +1 -1
  142. data/lib/sequel/plugins/finder.rb +4 -2
  143. data/lib/sequel/plugins/input_transformer.rb +1 -1
  144. data/lib/sequel/plugins/list.rb +5 -2
  145. data/lib/sequel/plugins/many_through_many.rb +1 -1
  146. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  147. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  148. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  149. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  150. data/lib/sequel/plugins/paged_operations.rb +181 -0
  151. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  152. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  153. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  154. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  155. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  156. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  157. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  158. data/lib/sequel/plugins/sql_comments.rb +1 -1
  159. data/lib/sequel/plugins/static_cache.rb +38 -0
  160. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  161. data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
  162. data/lib/sequel/plugins/validate_associated.rb +22 -12
  163. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  164. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  165. data/lib/sequel/version.rb +2 -2
  166. metadata +79 -6
@@ -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,
@@ -185,8 +185,12 @@ module Sequel
185
185
  case arg
186
186
  when Sequel::SQL::Blob
187
187
  {:value=>arg, :type=>17, :format=>1}
188
- when DateTime, Time
189
- 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:
190
194
  else
191
195
  arg
192
196
  end
@@ -668,6 +672,7 @@ module Sequel
668
672
  # cursor usage.
669
673
  # :rows_per_fetch :: The number of rows per fetch (default 1000). Higher
670
674
  # numbers result in fewer queries but greater memory use.
675
+ # :skip_transaction :: Same as :hold, but :hold takes priority.
671
676
  #
672
677
  # Usage:
673
678
  #
@@ -760,13 +765,13 @@ module Sequel
760
765
 
761
766
  # Use a cursor to fetch groups of records at a time, yielding them to the block.
762
767
  def cursor_fetch_rows(sql)
763
- server_opts = {:server=>@opts[:server] || :read_only}
764
768
  cursor = @opts[:cursor]
765
- hold = cursor[:hold]
769
+ hold = cursor.fetch(:hold){cursor[:skip_transaction]}
770
+ server_opts = {:server=>@opts[:server] || :read_only, :skip_transaction=>hold}
766
771
  cursor_name = quote_identifier(cursor[:cursor_name] || 'sequel_cursor')
767
772
  rows_per_fetch = cursor[:rows_per_fetch].to_i
768
773
 
769
- db.public_send(*(hold ? [:synchronize, server_opts[:server]] : [:transaction, server_opts])) do
774
+ db.transaction(server_opts) do
770
775
  begin
771
776
  execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR WITH#{'OUT' unless hold} HOLD FOR #{sql}", server_opts)
772
777
  rows_per_fetch = 1000 if rows_per_fetch <= 0
@@ -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,9 +921,14 @@ module Sequel
857
921
  true
858
922
  end
859
923
 
860
- # 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.
861
930
  def supports_skip_locked?
862
- !db.mariadb? && db.server_version >= 80000
931
+ db.server_version >= (db.mariadb? ? 100600 : 80000)
863
932
  end
864
933
 
865
934
  # Check the database setting for whether fractional timestamps
@@ -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)
@@ -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
@@ -178,6 +179,13 @@ module Sequel
178
179
  ''
179
180
  end
180
181
 
182
+ # Support min/max integer values on Oracle only if
183
+ # they use a NUMBER column with a fixed precision
184
+ # and no scale.
185
+ def column_schema_integer_min_max_values(column)
186
+ super if column[:db_type] =~ /NUMBER\(\d+\)/i || (column[:db_type] == 'NUMBER' && column[:column_size].is_a?(Integer) && column[:scale] == 0)
187
+ end
188
+
181
189
  def create_sequence_sql(name, opts=OPTS)
182
190
  "CREATE SEQUENCE #{quote_identifier(name)} start with #{opts [:start_with]||1} increment by #{opts[:increment_by]||1} nomaxvalue"
183
191
  end
@@ -319,6 +327,8 @@ module Sequel
319
327
  end
320
328
 
321
329
  module DatasetMethods
330
+ include AutoCastDateAndTime
331
+
322
332
  ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
323
333
  BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
324
334
 
@@ -378,7 +388,12 @@ module Sequel
378
388
  # Use a custom expression with EXISTS to determine whether a dataset
379
389
  # is empty.
380
390
  def empty?
381
- db[:dual].where(@opts[:offset] ? exists : unordered.exists).get(1) == nil
391
+ if @opts[:sql]
392
+ naked.each{return false}
393
+ true
394
+ else
395
+ db[:dual].where(@opts[:offset] ? exists : unordered.exists).get(1) == nil
396
+ end
382
397
  end
383
398
 
384
399
  # Oracle requires SQL standard datetimes
@@ -611,7 +626,7 @@ module Sequel
611
626
 
612
627
  # The strftime format to use when literalizing the time.
613
628
  def default_timestamp_format
614
- "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'"
629
+ "'%Y-%m-%d %H:%M:%S.%6N %:z'"
615
630
  end
616
631
 
617
632
  def empty_from_sql
@@ -648,11 +663,6 @@ module Sequel
648
663
  super
649
664
  end
650
665
 
651
- # Use a colon for the timestamp offset, since Oracle appears to require it.
652
- def format_timestamp_offset(hour, minute)
653
- sprintf("%+03i:%02i", hour, minute)
654
- end
655
-
656
666
  # Oracle doesn't support empty values when inserting.
657
667
  def insert_supports_empty_values?
658
668
  false