sequel 5.92.0 → 5.94.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/ado/access.rb +2 -2
  3. data/lib/sequel/adapters/ado.rb +1 -1
  4. data/lib/sequel/adapters/ibmdb.rb +1 -1
  5. data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
  6. data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
  7. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
  8. data/lib/sequel/adapters/jdbc/sqlserver.rb +1 -1
  9. data/lib/sequel/adapters/jdbc.rb +21 -7
  10. data/lib/sequel/adapters/mysql.rb +1 -1
  11. data/lib/sequel/adapters/mysql2.rb +2 -2
  12. data/lib/sequel/adapters/odbc.rb +1 -1
  13. data/lib/sequel/adapters/oracle.rb +16 -0
  14. data/lib/sequel/adapters/shared/db2.rb +8 -3
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  17. data/lib/sequel/adapters/shared/postgres.rb +206 -16
  18. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  19. data/lib/sequel/adapters/tinytds.rb +1 -1
  20. data/lib/sequel/adapters/trilogy.rb +1 -1
  21. data/lib/sequel/connection_pool/timed_queue.rb +1 -1
  22. data/lib/sequel/core.rb +1 -1
  23. data/lib/sequel/database/misc.rb +3 -3
  24. data/lib/sequel/database/query.rb +1 -1
  25. data/lib/sequel/database/schema_generator.rb +24 -10
  26. data/lib/sequel/database/schema_methods.rb +60 -26
  27. data/lib/sequel/dataset/prepared_statements.rb +68 -24
  28. data/lib/sequel/dataset/query.rb +10 -2
  29. data/lib/sequel/dataset/sql.rb +8 -5
  30. data/lib/sequel/extensions/async_thread_pool.rb +1 -1
  31. data/lib/sequel/extensions/caller_logging.rb +1 -3
  32. data/lib/sequel/extensions/eval_inspect.rb +1 -1
  33. data/lib/sequel/extensions/inflector.rb +2 -2
  34. data/lib/sequel/extensions/migration.rb +1 -1
  35. data/lib/sequel/extensions/pg_auto_parameterize.rb +5 -0
  36. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  37. data/lib/sequel/extensions/provenance.rb +1 -3
  38. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  39. data/lib/sequel/plugins/constraint_validations.rb +1 -1
  40. data/lib/sequel/plugins/json_serializer.rb +3 -3
  41. data/lib/sequel/plugins/unused_associations.rb +4 -2
  42. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  43. data/lib/sequel/sql.rb +7 -5
  44. data/lib/sequel/version.rb +1 -1
  45. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df46d23e3607466eb34477a5ba9c31d14fd57294275088599f8ef4d561179faf
4
- data.tar.gz: 9566fcb614fded77e5e20c929c9ed5cb6786e0548c3e59837ae2dd1a1877762d
3
+ metadata.gz: 05c58f7009f56c4891e863d95ccd90eeb7acdc03f192979ff0d4a203fa1924d4
4
+ data.tar.gz: 957d465eebddd41b7de48475af26b04f4ae6fb387662257b97b9bdbc682eb197
5
5
  SHA512:
6
- metadata.gz: 8375cb4204be00b492b841fea5427dce809e43d119c6fd9f559cbfa1b4b9f652e752d22e52a93942a8a228166829d4227202e0f783a89f790d4fad40a8eb0963
7
- data.tar.gz: 4c6bbc4bd1172ed8c5117bd628651757b8685fab754b4d664ceb3254917fbeb58d778bccd3b8dbe998e4694f185baa4e630910dc1de8fe1b5026d23e95c215b4
6
+ metadata.gz: eb72cd090c58b8246b4a679082f9426f9aecf2d4815dffa2d96fe3be5a15b3e41b9ae7dd17effee49bfab82ac54c11d5d7d6aa67ff2a8aa54040145f5c72a3b0
7
+ data.tar.gz: 031fcb089d79550e485be488ec6d4b0d84024e4f686ebe953a08189c5b9d6e490c2351246b4258d8b78e9efafd013db61738f19e40a04afbb9fb1ec1796e5e6a
@@ -281,7 +281,7 @@ module Sequel
281
281
  fetch_ado_schema(:columns, [nil,nil,table_name.to_s,nil]) do |row|
282
282
  rows << AdoSchema::Column.new(row)
283
283
  end
284
- rows.sort!{|a,b| a["ORDINAL_POSITION"] <=> b["ORDINAL_POSITION"]}
284
+ rows.sort_by! { |a| a["ORDINAL_POSITION"] }
285
285
  end
286
286
 
287
287
  def ado_schema_foreign_keys(table_name)
@@ -289,7 +289,7 @@ module Sequel
289
289
  fetch_ado_schema(:foreign_keys, [nil,nil,nil,nil,nil,table_name.to_s]) do |row|
290
290
  rows << row
291
291
  end
292
- rows.sort!{|a,b| a["ORDINAL"] <=> b["ORDINAL"]}
292
+ rows.sort_by! { |a| a["ORDINAL"] }
293
293
  end
294
294
 
295
295
  def fetch_ado_schema(type, criteria=[])
@@ -234,7 +234,7 @@ module Sequel
234
234
  end
235
235
 
236
236
  def disconnect_error?(e, opts)
237
- super || (e.is_a?(::WIN32OLERuntimeError) && e.message =~ /Communication link failure/)
237
+ super || (e.is_a?(::WIN32OLERuntimeError) && e.message.include?('Communication link failure'))
238
238
  end
239
239
 
240
240
  def rollback_transaction(conn, opts=OPTS)
@@ -324,7 +324,7 @@ module Sequel
324
324
  when Numeric
325
325
  v.to_s
326
326
  when Date, Time
327
- literal(v).gsub("'", '')
327
+ literal(v).delete("'")
328
328
  else
329
329
  v
330
330
  end
@@ -26,7 +26,7 @@ module Sequel
26
26
 
27
27
  # Raise a disconnect error if the SQL state of the cause of the exception indicates so.
28
28
  def disconnect_error?(exception, opts)
29
- exception.message =~ /\ACommunications link failure/ || super
29
+ exception.message.start_with?("Communications link failure") || super
30
30
  end
31
31
 
32
32
  # Get the last inserted id using LAST_INSERT_ID().
@@ -64,7 +64,7 @@ module Sequel
64
64
 
65
65
  # Convert tinyint(1) type to boolean
66
66
  def schema_column_type(db_type)
67
- db_type =~ /\Atinyint\(1\)/ ? :boolean : super
67
+ db_type.start_with?("tinyint(1)") ? :boolean : super
68
68
  end
69
69
 
70
70
  # Run the default connection setting SQL statements.
@@ -55,7 +55,7 @@ module Sequel
55
55
  end
56
56
 
57
57
  def disconnect_error?(exception, opts)
58
- super || exception.message =~ /\AClosed Connection/
58
+ super || exception.message.start_with?("Closed Connection")
59
59
  end
60
60
 
61
61
  # Default the fetch size for statements to 100, similar to the oci8-based oracle adapter.
@@ -98,7 +98,7 @@ module Sequel
98
98
 
99
99
  # Whether the given exception is due to a foreign key error.
100
100
  def foreign_key_error?(exception)
101
- exception.message =~ /query does not return ResultSet/
101
+ exception.message.include?('query does not return ResultSet')
102
102
  end
103
103
 
104
104
  # Use getLong instead of getInt for converting integers on SQLite, since SQLite does not enforce a limit of 2**32.
@@ -84,7 +84,7 @@ module Sequel
84
84
  end
85
85
 
86
86
  def disconnect_error?(exception, opts)
87
- super || (exception.message =~ /connection is closed/)
87
+ super || (exception.message.include?('connection is closed'))
88
88
  end
89
89
  end
90
90
  end
@@ -305,17 +305,31 @@ module Sequel
305
305
  m = output_identifier_meth
306
306
  schema, table = metadata_schema_and_table(table, opts)
307
307
  foreign_keys = {}
308
+
308
309
  metadata(:getImportedKeys, nil, schema, table) do |r|
309
- if fk = foreign_keys[r[:fk_name]]
310
- fk[:columns] << [r[:key_seq], m.call(r[:fkcolumn_name])]
311
- fk[:key] << [r[:key_seq], m.call(r[:pkcolumn_name])]
312
- elsif r[:fk_name]
313
- foreign_keys[r[:fk_name]] = {:name=>m.call(r[:fk_name]), :columns=>[[r[:key_seq], m.call(r[:fkcolumn_name])]], :table=>m.call(r[:pktable_name]), :key=>[[r[:key_seq], m.call(r[:pkcolumn_name])]]}
310
+ next unless fk_name = r[:fk_name]
311
+
312
+ key_seq = r[:key_seq]
313
+ columns = [key_seq, m.call(r[:fkcolumn_name])]
314
+ key = [key_seq, m.call(r[:pkcolumn_name])]
315
+
316
+ if fk = foreign_keys[fk_name]
317
+ fk[:columns] << columns
318
+ fk[:key] << key
319
+ else
320
+ foreign_keys[fk_name] = {
321
+ :name=>m.call(fk_name),
322
+ :columns=>[columns],
323
+ :table=>m.call(r[:pktable_name]),
324
+ :key=>[key]
325
+ }
314
326
  end
315
327
  end
328
+
329
+ fk_keys = [:columns, :key]
316
330
  foreign_keys.values.each do |fk|
317
- [:columns, :key].each do |k|
318
- fk[k] = fk[k].sort.map{|_, v| v}
331
+ fk_keys.each do |k|
332
+ fk[k].sort!.map!{|_, v| v}
319
333
  end
320
334
  end
321
335
  end
@@ -290,7 +290,7 @@ module Sequel
290
290
 
291
291
  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
292
292
  def schema_column_type(db_type)
293
- convert_tinyint_to_bool && db_type =~ /\Atinyint\(1\)/ ? :boolean : super
293
+ convert_tinyint_to_bool && db_type.start_with?("tinyint(1)") ? :boolean : super
294
294
  end
295
295
  end
296
296
 
@@ -221,13 +221,13 @@ module Sequel
221
221
  super ||
222
222
  ((conn = opts[:conn]) && !conn.ping) ||
223
223
  (e.is_a?(::Mysql2::Error) &&
224
- (e.sql_state =~ /\A08/ ||
224
+ ((e.sql_state && e.sql_state.start_with?("08")) ||
225
225
  MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message)))
226
226
  end
227
227
 
228
228
  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
229
229
  def schema_column_type(db_type)
230
- convert_tinyint_to_bool && db_type =~ /\Atinyint\(1\)/ ? :boolean : super
230
+ convert_tinyint_to_bool && db_type.start_with?("tinyint(1)") ? :boolean : super
231
231
  end
232
232
  end
233
233
 
@@ -81,7 +81,7 @@ module Sequel
81
81
  end
82
82
 
83
83
  def disconnect_error?(e, opts)
84
- super || (e.is_a?(::ODBC::Error) && /\A08S01/.match(e.message))
84
+ super || (e.is_a?(::ODBC::Error) && e.message.start_with?("08S01"))
85
85
  end
86
86
  end
87
87
 
@@ -353,6 +353,20 @@ module Sequel
353
353
  i = prepared_args.length
354
354
  LiteralString.new(":#{i}")
355
355
  end
356
+
357
+ # Avoid infinite recursion on Oracle <12 for datasets with limits
358
+ # (which are implemented via subqueries). If the given dataset's
359
+ # prepared args are the same object as current dataset's, call the
360
+ # standard Sequel::Dataset#subselect_sql_append method, instead
361
+ # of calling super (which will call prepared_sql and result in
362
+ # infinite recursion).
363
+ def subselect_sql_append(sql, ds)
364
+ if !supports_fetch_next_rows? && ds.opts[:prepared_args].equal?(@opts[:prepared_args])
365
+ orig_subselect_sql_append(sql, ds)
366
+ else
367
+ super
368
+ end
369
+ end
356
370
  end
357
371
 
358
372
  BindArgumentMethods = prepared_statements_module(:bind, ArgumentMapper)
@@ -383,6 +397,8 @@ module Sequel
383
397
 
384
398
  private
385
399
 
400
+ alias orig_subselect_sql_append subselect_sql_append
401
+
386
402
  def literal_other_append(sql, v)
387
403
  case v
388
404
  when OraDate
@@ -108,7 +108,7 @@ module Sequel
108
108
  from(name).first
109
109
  true
110
110
  rescue DatabaseError => e
111
- if e.to_s =~ /Operation not allowed for reason code "7" on table/ && v == false
111
+ if e.to_s.include?('Operation not allowed for reason code "7" on table') && v == false
112
112
  # table probably needs reorg
113
113
  reorg(name)
114
114
  v = true
@@ -175,8 +175,13 @@ module Sequel
175
175
  # Supply columns with NOT NULL if they are part of a composite
176
176
  # primary key or unique constraint
177
177
  def column_list_sql(g)
178
- ks = []
179
- g.constraints.each{|c| ks = c[:columns] if [:primary_key, :unique].include?(c[:type])}
178
+ ks = {}
179
+ g.constraints.each do |c|
180
+ case c[:type]
181
+ when :primary_key, :unique
182
+ c[:columns].each{|c| ks[c] = true}
183
+ end
184
+ end
180
185
  g.columns.each{|c| c[:null] = false if ks.include?(c[:name]) }
181
186
  super
182
187
  end
@@ -705,7 +705,7 @@ module Sequel
705
705
 
706
706
  # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
707
707
  def nolock
708
- lock_style(:dirty)
708
+ cached_lock_style_dataset(:_nolock_ds, :dirty)
709
709
  end
710
710
 
711
711
  # Uses OUTER APPLY to join the given table into the current dataset.
@@ -456,7 +456,7 @@ module Sequel
456
456
  [pk]
457
457
  elsif !(pkc = generator.constraints.select{|con| con[:type] == :primary_key}).empty?
458
458
  pkc.first[:columns]
459
- elsif !(pkc = generator.columns.select{|con| con[:primary_key] == true}).empty?
459
+ elsif !(pkc = generator.columns.select{|con| con[:primary_key]}).empty?
460
460
  pkc.map{|c| c[:name]}
461
461
  end
462
462
  else
@@ -787,7 +787,7 @@ module Sequel
787
787
 
788
788
  # Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
789
789
  def for_share
790
- lock_style(:share)
790
+ cached_lock_style_dataset(:_for_share_ds, :share)
791
791
  end
792
792
 
793
793
  # Adds full text filter
@@ -48,7 +48,7 @@ module Sequel
48
48
  TYPE_TRANSLATOR_DATE = date.freeze
49
49
  bytea = Object.new
50
50
  def bytea.call(str)
51
- str = if str =~ /\A\\x/
51
+ str = if str.start_with?('\\x')
52
52
  # PostgreSQL 9.0+ bytea hex format
53
53
  str[2..-1].gsub(/(..)/){|s| s.to_i(16).chr}
54
54
  else
@@ -101,7 +101,7 @@ module Sequel
101
101
 
102
102
  def self.mock_adapter_setup(db)
103
103
  db.instance_exec do
104
- @server_version = 150000
104
+ @server_version = 170000
105
105
  initialize_postgres_adapter
106
106
  extend(MockAdapterDatabaseMethods)
107
107
  end
@@ -141,6 +141,17 @@ module Sequel
141
141
  @operations << {:op => :add_constraint, :type => :exclude, :elements => elements}.merge!(opts)
142
142
  end
143
143
 
144
+ # Alter an existing constraint. Options:
145
+ # :deferrable :: Modify deferrable setting for constraint (PostgreSQL 9.4+):
146
+ # true :: DEFERRABLE INITIALLY DEFERRED
147
+ # false :: NOT DEFERRABLE
148
+ # :immediate :: DEFERRABLE INITIALLY IMMEDIATE
149
+ # :enforced :: Set true to use ENFORCED, or false to use NOT ENFORCED (PostgreSQL 18+)
150
+ # :inherit :: Set true to use INHERIT, or false to use NO INHERIT (PostgreSQL 18+)
151
+ def alter_constraint(name, opts=OPTS)
152
+ @operations << {:op => :alter_constraint, :name => name}.merge!(opts)
153
+ end
154
+
144
155
  # Validate the constraint with the given name, which should have
145
156
  # been added previously with NOT VALID.
146
157
  def validate_constraint(name)
@@ -335,7 +346,7 @@ module Sequel
335
346
  hash = {}
336
347
  _check_constraints_ds.where_each(:conrelid=>regclass_oid(table)) do |row|
337
348
  constraint = m.call(row[:constraint])
338
- entry = hash[constraint] ||= {:definition=>row[:definition], :columns=>[]}
349
+ entry = hash[constraint] ||= {:definition=>row[:definition], :columns=>[], :validated=>row[:validated], :enforced=>row[:enforced]}
339
350
  entry[:columns] << m.call(row[:column]) if row[:column]
340
351
  end
341
352
 
@@ -601,6 +612,8 @@ module Sequel
601
612
  :on_update=>fklod_map[row[:on_update]],
602
613
  :on_delete=>fklod_map[row[:on_delete]],
603
614
  :deferrable=>row[:deferrable],
615
+ :validated=>row[:validated],
616
+ :enforced=>row[:enforced],
604
617
  :table=>schema ? SQL::QualifiedIdentifier.new(m.call(row[:schema]), m.call(row[:table])) : m.call(row[:table]),
605
618
  }
606
619
 
@@ -898,11 +911,15 @@ module Sequel
898
911
 
899
912
  # Dataset used to retrieve CHECK constraint information
900
913
  def _check_constraints_ds
901
- @_check_constraints_ds ||= metadata_dataset.
902
- from{pg_constraint.as(:co)}.
903
- left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
904
- where(:contype=>'c').
905
- select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
914
+ @_check_constraints_ds ||= begin
915
+ ds = metadata_dataset.
916
+ from{pg_constraint.as(:co)}.
917
+ left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
918
+ where(:contype=>'c').
919
+ select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
920
+
921
+ _add_validated_enforced_constraint_columns(ds)
922
+ end
906
923
  end
907
924
 
908
925
  # Dataset used to retrieve foreign keys referenced by a table
@@ -968,6 +985,28 @@ module Sequel
968
985
  ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
969
986
  end
970
987
 
988
+ _add_validated_enforced_constraint_columns(ds)
989
+ end
990
+
991
+ def _add_validated_enforced_constraint_columns(ds)
992
+ validated_cond = if server_version >= 90100
993
+ Sequel[:convalidated]
994
+ # :nocov:
995
+ else
996
+ Sequel.cast(true, TrueClass)
997
+ # :nocov:
998
+ end
999
+ ds = ds.select_append(validated_cond.as(:validated))
1000
+
1001
+ enforced_cond = if server_version >= 180000
1002
+ Sequel[:conenforced]
1003
+ # :nocov:
1004
+ else
1005
+ Sequel.cast(true, TrueClass)
1006
+ # :nocov:
1007
+ end
1008
+ ds = ds.select_append(enforced_cond.as(:enforced))
1009
+
971
1010
  ds
972
1011
  end
973
1012
 
@@ -1140,6 +1179,31 @@ module Sequel
1140
1179
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
1141
1180
  end
1142
1181
 
1182
+ def alter_table_alter_constraint_sql(table, op)
1183
+ sql = String.new
1184
+ sql << "ALTER CONSTRAINT #{quote_identifier(op[:name])}"
1185
+
1186
+ constraint_deferrable_sql_append(sql, op[:deferrable])
1187
+
1188
+ case op[:enforced]
1189
+ when nil
1190
+ when false
1191
+ sql << " NOT ENFORCED"
1192
+ else
1193
+ sql << " ENFORCED"
1194
+ end
1195
+
1196
+ case op[:inherit]
1197
+ when nil
1198
+ when false
1199
+ sql << " NO INHERIT"
1200
+ else
1201
+ sql << " INHERIT"
1202
+ end
1203
+
1204
+ sql
1205
+ end
1206
+
1143
1207
  def alter_table_generator_class
1144
1208
  Postgres::AlterTableGenerator
1145
1209
  end
@@ -1233,9 +1297,9 @@ module Sequel
1233
1297
  end
1234
1298
 
1235
1299
  # PostgreSQL can't combine rename_column operations, and it can combine
1236
- # the custom validate_constraint operation.
1300
+ # validate_constraint and alter_constraint operations.
1237
1301
  def combinable_alter_table_op?(op)
1238
- (super || op[:op] == :validate_constraint) && op[:op] != :rename_column
1302
+ (super || op[:op] == :validate_constraint || op[:op] == :alter_constraint) && op[:op] != :rename_column
1239
1303
  end
1240
1304
 
1241
1305
  VALID_CLIENT_MIN_MESSAGES = %w'DEBUG5 DEBUG4 DEBUG3 DEBUG2 DEBUG1 LOG NOTICE WARNING ERROR FATAL PANIC'.freeze.each(&:freeze)
@@ -1269,7 +1333,7 @@ module Sequel
1269
1333
  sqls
1270
1334
  end
1271
1335
 
1272
- # Handle exclusion constraints.
1336
+ # Handle PostgreSQL-specific constraint features.
1273
1337
  def constraint_definition_sql(constraint)
1274
1338
  case type = constraint[:type]
1275
1339
  when :exclude
@@ -1293,6 +1357,9 @@ module Sequel
1293
1357
  end
1294
1358
  else # when :foreign_key, :check
1295
1359
  sql = super
1360
+ if constraint[:not_enforced]
1361
+ sql << " NOT ENFORCED"
1362
+ end
1296
1363
  if constraint[:not_valid]
1297
1364
  sql << " NOT VALID"
1298
1365
  end
@@ -1300,6 +1367,13 @@ module Sequel
1300
1367
  end
1301
1368
  end
1302
1369
 
1370
+ def column_definition_add_references_sql(sql, column)
1371
+ super
1372
+ if column[:not_enforced]
1373
+ sql << " NOT ENFORCED"
1374
+ end
1375
+ end
1376
+
1303
1377
  def database_specific_error_class_from_sqlstate(sqlstate)
1304
1378
  if sqlstate == '23P01'
1305
1379
  ExclusionConstraintViolation
@@ -1369,7 +1443,8 @@ module Sequel
1369
1443
  # SQL statement to create database function.
1370
1444
  def create_function_sql(name, definition, opts=OPTS)
1371
1445
  args = opts[:args]
1372
- if !opts[:args].is_a?(Array) || !opts[:args].any?{|a| Array(a).length == 3 and %w'OUT INOUT'.include?(a[2].to_s)}
1446
+ in_out = %w'OUT INOUT'
1447
+ if (!opts[:args].is_a?(Array) || !opts[:args].any?{|a| Array(a).length == 3 && in_out.include?(a[2].to_s)})
1373
1448
  returns = opts[:returns] || 'void'
1374
1449
  end
1375
1450
  language = opts[:language] || 'SQL'
@@ -1888,14 +1963,66 @@ module Sequel
1888
1963
  super
1889
1964
  end
1890
1965
 
1891
- # Return the results of an EXPLAIN query as a string
1966
+ # Return the results of an EXPLAIN query. Boolean options:
1967
+ #
1968
+ # :analyze :: Use the ANALYZE option.
1969
+ # :buffers :: Use the BUFFERS option.
1970
+ # :costs :: Use the COSTS option.
1971
+ # :generic_plan :: Use the GENERIC_PLAN option.
1972
+ # :memory :: Use the MEMORY option.
1973
+ # :settings :: Use the SETTINGS option.
1974
+ # :summary :: Use the SUMMARY option.
1975
+ # :timing :: Use the TIMING option.
1976
+ # :verbose :: Use the VERBOSE option.
1977
+ # :wal :: Use the WAL option.
1978
+ #
1979
+ # Non boolean options:
1980
+ #
1981
+ # :format :: Use the FORMAT option to change the format of the
1982
+ # returned value. Values can be :text, :xml, :json,
1983
+ # or :yaml.
1984
+ # :serialize :: Use the SERIALIZE option to get timing on
1985
+ # serialization. Values can be :none, :text, or
1986
+ # :binary.
1987
+ #
1988
+ # See the PostgreSQL EXPLAIN documentation for an explanation of
1989
+ # what each option does.
1990
+ #
1991
+ # In most cases, the return value is a single string. However,
1992
+ # using the <tt>format: :json</tt> option can result in the return
1993
+ # value being an array containing a hash.
1892
1994
  def explain(opts=OPTS)
1893
- with_sql((opts[:analyze] ? 'EXPLAIN ANALYZE ' : 'EXPLAIN ') + select_sql).map(:'QUERY PLAN').join("\r\n")
1995
+ rows = clone(:append_sql=>explain_sql_string_origin(opts)).map(:'QUERY PLAN')
1996
+
1997
+ if rows.length == 1
1998
+ rows[0]
1999
+ elsif rows.all?{|row| String === row}
2000
+ rows.join("\r\n")
2001
+ # :nocov:
2002
+ else
2003
+ # This branch is unreachable in tests, but it seems better to just return
2004
+ # all rows than throw in error if this case actually happens.
2005
+ rows
2006
+ # :nocov:
2007
+ end
2008
+ end
2009
+
2010
+ # Return a cloned dataset which will use FOR KEY SHARE to lock returned rows.
2011
+ # Supported on PostgreSQL 9.3+.
2012
+ def for_key_share
2013
+ cached_lock_style_dataset(:_for_key_share_ds, :key_share)
2014
+ end
2015
+
2016
+ # Return a cloned dataset which will use FOR NO KEY UPDATE to lock returned rows.
2017
+ # This is generally a better choice than using for_update on PostgreSQL, unless
2018
+ # you will be deleting the row or modifying a key column. Supported on PostgreSQL 9.3+.
2019
+ def for_no_key_update
2020
+ cached_lock_style_dataset(:_for_no_key_update_ds, :no_key_update)
1894
2021
  end
1895
2022
 
1896
2023
  # Return a cloned dataset which will use FOR SHARE to lock returned rows.
1897
2024
  def for_share
1898
- lock_style(:share)
2025
+ cached_lock_style_dataset(:_for_share_ds, :share)
1899
2026
  end
1900
2027
 
1901
2028
  # Run a full text search on PostgreSQL. By default, searching for the inclusion
@@ -2417,7 +2544,65 @@ module Sequel
2417
2544
  c ||= true
2418
2545
  end
2419
2546
  end
2547
+
2548
+ EXPLAIN_BOOLEAN_OPTIONS = {}
2549
+ %w[analyze verbose costs settings generic_plan buffers wal timing summary memory].each do |str|
2550
+ EXPLAIN_BOOLEAN_OPTIONS[str.to_sym] = str.upcase.freeze
2551
+ end
2552
+ EXPLAIN_BOOLEAN_OPTIONS.freeze
2553
+
2554
+ EXPLAIN_NONBOOLEAN_OPTIONS = {
2555
+ :serialize => {:none=>"SERIALIZE NONE", :text=>"SERIALIZE TEXT", :binary=>"SERIALIZE BINARY"}.freeze,
2556
+ :format => {:text=>"FORMAT TEXT", :xml=>"FORMAT XML", :json=>"FORMAT JSON", :yaml=>"FORMAT YAML"}.freeze
2557
+ }.freeze
2420
2558
 
2559
+ # A mutable string used as the prefix when explaining a query.
2560
+ def explain_sql_string_origin(opts)
2561
+ origin = String.new
2562
+ origin << 'EXPLAIN '
2563
+
2564
+ # :nocov:
2565
+ if server_version < 90000
2566
+ if opts[:analyze]
2567
+ origin << 'ANALYZE '
2568
+ end
2569
+
2570
+ return origin
2571
+ end
2572
+ # :nocov:
2573
+
2574
+ comma = nil
2575
+ paren = "("
2576
+
2577
+ add_opt = lambda do |str, value|
2578
+ origin << paren if paren
2579
+ origin << comma if comma
2580
+ origin << str
2581
+ origin << " FALSE" unless value
2582
+ comma ||= ', '
2583
+ paren &&= nil
2584
+ end
2585
+
2586
+ EXPLAIN_BOOLEAN_OPTIONS.each do |key, str|
2587
+ unless (value = opts[key]).nil?
2588
+ add_opt.call(str, value)
2589
+ end
2590
+ end
2591
+
2592
+ EXPLAIN_NONBOOLEAN_OPTIONS.each do |key, e_opts|
2593
+ if value = opts[key]
2594
+ if str = e_opts[value]
2595
+ add_opt.call(str, true)
2596
+ else
2597
+ raise Sequel::Error, "unrecognized value for Dataset#explain #{key.inspect} option: #{value.inspect}"
2598
+ end
2599
+ end
2600
+ end
2601
+
2602
+ origin << ') ' unless paren
2603
+ origin
2604
+ end
2605
+
2421
2606
  # Add ON CONFLICT clause if it should be used
2422
2607
  def insert_conflict_sql(sql)
2423
2608
  if opts = @opts[:insert_conflict]
@@ -2613,8 +2798,13 @@ module Sequel
2613
2798
  # Use SKIP LOCKED if skipping locked rows.
2614
2799
  def select_lock_sql(sql)
2615
2800
  lock = @opts[:lock]
2616
- if lock == :share
2801
+ case lock
2802
+ when :share
2617
2803
  sql << ' FOR SHARE'
2804
+ when :no_key_update
2805
+ sql << ' FOR NO KEY UPDATE'
2806
+ when :key_share
2807
+ sql << ' FOR KEY SHARE'
2618
2808
  else
2619
2809
  super
2620
2810
  end
@@ -95,14 +95,14 @@ module Sequel
95
95
  metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
96
96
  if opts[:only_autocreated]
97
97
  # If specifically asked for only autocreated indexes, then return those an only those
98
- next unless r[:name] =~ /\Asqlite_autoindex_/
98
+ next unless r[:name].start_with?('sqlite_autoindex_')
99
99
  elsif r.has_key?(:origin)
100
100
  # If origin is set, then only exclude primary key indexes and partial indexes
101
101
  next if r[:origin] == 'pk'
102
102
  next if r[:partial].to_i == 1
103
103
  else
104
104
  # When :origin key not present, assume any autoindex could be a primary key one and exclude it
105
- next if r[:name] =~ /\Asqlite_autoindex_/
105
+ next if r[:name].start_with?('sqlite_autoindex_')
106
106
  end
107
107
 
108
108
  indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
@@ -302,7 +302,7 @@ module Sequel
302
302
 
303
303
  # A name to use for the backup table
304
304
  def backup_table_name(table, opts=OPTS)
305
- table = table.gsub('`', '')
305
+ table = table.delete('`')
306
306
  (opts[:times]||1000).times do |i|
307
307
  table_name = "#{table}_backup#{i}"
308
308
  return table_name unless table_exists?(table_name)
@@ -14,7 +14,7 @@ module Sequel
14
14
  opts = server_opts(server)
15
15
  opts[:username] = opts[:user]
16
16
  c = TinyTds::Client.new(opts)
17
- c.query_options.merge!(:cache_rows=>false)
17
+ c.query_options[:cache_rows] = false
18
18
 
19
19
  # SEQUEL6: Default to ansi: true
20
20
  if opts[:ansi]
@@ -83,7 +83,7 @@ module Sequel
83
83
 
84
84
  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
85
85
  def schema_column_type(db_type)
86
- db_type =~ /\Atinyint\(1\)/ ? :boolean : super
86
+ db_type.start_with?("tinyint(1)") ? :boolean : super
87
87
  end
88
88
  end
89
89
 
@@ -236,7 +236,7 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
236
236
  def preconnect(concurrent = false)
237
237
  if concurrent
238
238
  if times = sync{@max_size > (size = @size[0]) ? @max_size - size : false}
239
- times.times.map{Thread.new{if conn = try_make_new; @queue.push(conn) end}}.map(&:value)
239
+ Array.new(times){Thread.new{if conn = try_make_new; @queue.push(conn) end}}.map(&:value)
240
240
  end
241
241
  else
242
242
  while conn = try_make_new
data/lib/sequel/core.rb CHANGED
@@ -403,7 +403,7 @@ module Sequel
403
403
  when -1, 0
404
404
  vr.instance_exec(&block)
405
405
  else
406
- block.call(vr)
406
+ yield(vr)
407
407
  end
408
408
  end
409
409