sequel 5.57.0 → 5.60.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +25 -0
  4. data/bin/sequel +11 -3
  5. data/doc/cheat_sheet.rdoc +8 -0
  6. data/doc/opening_databases.rdoc +10 -6
  7. data/doc/release_notes/5.58.0.txt +31 -0
  8. data/doc/release_notes/5.59.0.txt +73 -0
  9. data/doc/release_notes/5.60.0.txt +22 -0
  10. data/doc/testing.rdoc +1 -1
  11. data/lib/sequel/adapters/jdbc/derby.rb +5 -0
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  14. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
  15. data/lib/sequel/adapters/jdbc.rb +5 -5
  16. data/lib/sequel/adapters/mock.rb +1 -1
  17. data/lib/sequel/adapters/mysql.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -1
  19. data/lib/sequel/adapters/postgres.rb +58 -17
  20. data/lib/sequel/adapters/shared/db2.rb +28 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +35 -1
  22. data/lib/sequel/adapters/shared/mysql.rb +6 -0
  23. data/lib/sequel/adapters/shared/oracle.rb +70 -1
  24. data/lib/sequel/adapters/shared/postgres.rb +94 -18
  25. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  26. data/lib/sequel/adapters/sqlite.rb +1 -1
  27. data/lib/sequel/ast_transformer.rb +1 -1
  28. data/lib/sequel/database/misc.rb +2 -2
  29. data/lib/sequel/database/schema_generator.rb +1 -0
  30. data/lib/sequel/database/schema_methods.rb +3 -0
  31. data/lib/sequel/dataset/actions.rb +49 -0
  32. data/lib/sequel/dataset/features.rb +5 -0
  33. data/lib/sequel/dataset/query.rb +62 -0
  34. data/lib/sequel/dataset/sql.rb +114 -27
  35. data/lib/sequel/extensions/date_arithmetic.rb +35 -7
  36. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  37. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  38. data/lib/sequel/extensions/pg_array.rb +2 -2
  39. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  40. data/lib/sequel/extensions/pg_enum.rb +1 -1
  41. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  42. data/lib/sequel/extensions/pg_hstore_ops.rb +3 -3
  43. data/lib/sequel/extensions/pg_inet.rb +2 -2
  44. data/lib/sequel/extensions/pg_interval.rb +1 -1
  45. data/lib/sequel/extensions/pg_json.rb +1 -1
  46. data/lib/sequel/extensions/pg_json_ops.rb +55 -3
  47. data/lib/sequel/extensions/pg_multirange.rb +2 -2
  48. data/lib/sequel/extensions/pg_range.rb +2 -2
  49. data/lib/sequel/extensions/pg_row.rb +2 -2
  50. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  51. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  52. data/lib/sequel/model/associations.rb +18 -6
  53. data/lib/sequel/model/base.rb +17 -7
  54. data/lib/sequel/model/exceptions.rb +1 -1
  55. data/lib/sequel/model/inflections.rb +6 -6
  56. data/lib/sequel/plugins/auto_validations.rb +1 -1
  57. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  58. data/lib/sequel/plugins/dirty.rb +1 -1
  59. data/lib/sequel/plugins/insert_conflict.rb +1 -1
  60. data/lib/sequel/plugins/json_serializer.rb +1 -1
  61. data/lib/sequel/plugins/list.rb +3 -1
  62. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  63. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
  64. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  65. data/lib/sequel/plugins/serialization.rb +1 -1
  66. data/lib/sequel/plugins/sharding.rb +1 -1
  67. data/lib/sequel/plugins/sql_comments.rb +4 -4
  68. data/lib/sequel/plugins/subclasses.rb +1 -1
  69. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +3 -3
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +1 -1
  73. data/lib/sequel/version.rb +1 -1
  74. metadata +9 -16
@@ -32,6 +32,10 @@
32
32
  #
33
33
  # DB[:table].select(add.as(:d)).where(sub > Sequel::CURRENT_TIMESTAMP)
34
34
  #
35
+ # On most databases, the values you provide for years/months/days/etc. must
36
+ # be numeric values and not arbitrary SQL expressions. However, on PostgreSQL
37
+ # 9.4+, use of arbitrary SQL expressions is supported.
38
+ #
35
39
  # Related module: Sequel::SQL::DateAdd
36
40
 
37
41
  #
@@ -54,7 +58,16 @@ module Sequel
54
58
  interval = interval.parts
55
59
  end
56
60
  parts = {}
57
- interval.each{|k,v| parts[k] = -v unless v.nil?}
61
+ interval.each do |k,v|
62
+ case v
63
+ when nil
64
+ # ignore
65
+ when Numeric
66
+ parts[k] = -v
67
+ else
68
+ parts[k] = Sequel::SQL::NumericExpression.new(:*, v, -1)
69
+ end
70
+ end
58
71
  DateAdd.new(expr, parts, opts)
59
72
  end
60
73
  end
@@ -68,6 +81,7 @@ module Sequel
68
81
  module DatasetMethods
69
82
  DURATION_UNITS = [:years, :months, :days, :hours, :minutes, :seconds].freeze
70
83
  DEF_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s.freeze}).freeze
84
+ POSTGRES_DURATION_UNITS = DURATION_UNITS.zip([:years, :months, :days, :hours, :mins, :secs].map{|s| s.to_s.freeze}).freeze
71
85
  MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
72
86
  MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
73
87
  H2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s[0...-1].freeze}).freeze
@@ -87,14 +101,28 @@ module Sequel
87
101
 
88
102
  cast = case db_type = db.database_type
89
103
  when :postgres
90
- interval = String.new
91
- each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
92
- interval << "#{value} #{sql_unit} "
104
+ casted = Sequel.cast(expr, cast_type)
105
+
106
+ if db.server_version >= 90400
107
+ placeholder = []
108
+ vals = []
109
+ each_valid_interval_unit(h, POSTGRES_DURATION_UNITS) do |value, sql_unit|
110
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
111
+ vals << value
112
+ end
113
+ interval = Sequel.function(:make_interval, Sequel.lit(placeholder, *vals)) unless vals.empty?
114
+ else
115
+ parts = String.new
116
+ each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
117
+ parts << "#{value} #{sql_unit} "
118
+ end
119
+ interval = Sequel.cast(parts, :interval) unless parts.empty?
93
120
  end
94
- if interval.empty?
95
- return literal_append(sql, Sequel.cast(expr, cast_type))
121
+
122
+ if interval
123
+ return complex_expression_sql_append(sql, :+, [casted, interval])
96
124
  else
97
- return complex_expression_sql_append(sql, :+, [Sequel.cast(expr, cast_type), Sequel.cast(interval, :interval)])
125
+ return literal_append(sql, casted)
98
126
  end
99
127
  when :sqlite
100
128
  args = [expr]
@@ -77,7 +77,7 @@ module Sequel
77
77
  def duplicate_columns_handler_type(cols)
78
78
  handler = opts.fetch(:on_duplicate_columns){db.opts.fetch(:on_duplicate_columns, :warn)}
79
79
 
80
- if handler.respond_to?(:call)
80
+ if defined?(handler.call)
81
81
  handler.call(cols)
82
82
  else
83
83
  handler
@@ -3,7 +3,7 @@
3
3
  # The is_distinct_from extension adds the ability to use the
4
4
  # SQL standard IS DISTINCT FROM operator, which is similar to the
5
5
  # not equals operator, except that NULL values are considered
6
- # equal. Only PostgreSQL and H2 currently support this operator. On
6
+ # equal. PostgreSQL, SQLite 3.39+, and H2 currently support this operator. On
7
7
  # other databases, support is emulated.
8
8
  #
9
9
  # First, you need to load the extension into the database:
@@ -90,6 +90,8 @@ module Sequel
90
90
  case db.database_type
91
91
  when :postgres, :h2
92
92
  true
93
+ when :sqlite
94
+ db.sqlite_version >= 33900
93
95
  else
94
96
  false
95
97
  end
@@ -301,7 +301,7 @@ module Sequel
301
301
  end
302
302
  end
303
303
 
304
- unless Sequel::Postgres.respond_to?(:parse_pg_array)
304
+ unless defined?(Sequel::Postgres.parse_pg_array)
305
305
  require 'strscan'
306
306
 
307
307
  # PostgreSQL array parser that handles PostgreSQL array output format.
@@ -412,7 +412,7 @@ module Sequel
412
412
  @converter = converter
413
413
  end
414
414
 
415
- if Sequel::Postgres.respond_to?(:parse_pg_array)
415
+ if defined?(Sequel::Postgres.parse_pg_array)
416
416
  # :nocov:
417
417
  # Use sequel_pg's C-based parser if it has already been defined.
418
418
  def call(string)
@@ -158,7 +158,7 @@ module Sequel
158
158
  Sequel.function(:hstore, self, wrap_array(arg))
159
159
  end
160
160
  # :nocov:
161
- if Sequel.respond_to?(:hstore_op)
161
+ if defined?(Sequel.hstore_op)
162
162
  # :nocov:
163
163
  v = Sequel.hstore_op(v)
164
164
  end
@@ -144,7 +144,7 @@ module Sequel
144
144
  select_hash_groups(Sequel.cast(:enumtypid, Integer).as(:v), :enumlabel).freeze
145
145
  enum_labels.each_value(&:freeze)
146
146
 
147
- if respond_to?(:register_array_type)
147
+ if defined?(register_array_type)
148
148
  array_types = metadata_dataset.
149
149
  from(:pg_type).
150
150
  where(:oid=>enum_labels.keys).
@@ -76,7 +76,7 @@
76
76
  #
77
77
  # This extension integrates with the pg_array extension. If you plan
78
78
  # to use arrays of hstore types, load the pg_array extension before the
79
- # pg_interval extension:
79
+ # pg_hstore extension:
80
80
  #
81
81
  # DB.extension :pg_array, :pg_hstore
82
82
  #
@@ -296,7 +296,7 @@ module Sequel
296
296
 
297
297
  # Wrap argument in a PGArray if it is an array
298
298
  def wrap_input_array(obj)
299
- if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
299
+ if obj.is_a?(Array) && defined?(Sequel.pg_array)
300
300
  Sequel.pg_array(obj)
301
301
  else
302
302
  obj
@@ -305,7 +305,7 @@ module Sequel
305
305
 
306
306
  # Wrap argument in an Hstore if it is a hash
307
307
  def wrap_input_hash(obj)
308
- if obj.is_a?(Hash) && Sequel.respond_to?(:hstore)
308
+ if obj.is_a?(Hash) && defined?(Sequel.hstore)
309
309
  Sequel.hstore(obj)
310
310
  else
311
311
  obj
@@ -314,7 +314,7 @@ module Sequel
314
314
 
315
315
  # Wrap argument in a PGArrayOp if supported
316
316
  def wrap_output_array(obj)
317
- if Sequel.respond_to?(:pg_array_op)
317
+ if defined?(Sequel.pg_array_op)
318
318
  Sequel.pg_array_op(obj)
319
319
  else
320
320
  obj
@@ -49,13 +49,13 @@ module Sequel
49
49
  meth = IPAddr.method(:new)
50
50
  add_conversion_proc(869, meth)
51
51
  add_conversion_proc(650, meth)
52
- if respond_to?(:register_array_type)
52
+ if defined?(register_array_type)
53
53
  register_array_type('inet', :oid=>1041, :scalar_oid=>869)
54
54
  register_array_type('cidr', :oid=>651, :scalar_oid=>650)
55
55
  end
56
56
  end
57
57
 
58
- if respond_to?(:register_array_type)
58
+ if defined?(register_array_type)
59
59
  register_array_type('macaddr', :oid=>1040, :scalar_oid=>829)
60
60
  end
61
61
  @schema_type_classes[:ipaddr] = IPAddr
@@ -144,7 +144,7 @@ module Sequel
144
144
  db.instance_exec do
145
145
  extend_datasets(IntervalDatasetMethods)
146
146
  add_conversion_proc(1186, Postgres::IntervalDatabaseMethods::PARSER)
147
- if respond_to?(:register_array_type)
147
+ if defined?(register_array_type)
148
148
  register_array_type('interval', :oid=>1187, :scalar_oid=>1186)
149
149
  end
150
150
  @schema_type_classes[:interval] = ActiveSupport::Duration
@@ -227,7 +227,7 @@ module Sequel
227
227
  db.instance_exec do
228
228
  add_conversion_proc(114, method(:_db_parse_json))
229
229
  add_conversion_proc(3802, method(:_db_parse_jsonb))
230
- if respond_to?(:register_array_type)
230
+ if defined?(register_array_type)
231
231
  register_array_type('json', :oid=>199, :scalar_oid=>114)
232
232
  register_array_type('jsonb', :oid=>3807, :scalar_oid=>3802)
233
233
  end
@@ -123,6 +123,15 @@
123
123
  # c = Sequel.pg_jsonb_op(:c)
124
124
  # DB[:t].update(c['key1'] => 1.to_json, c['key2'] => "a".to_json)
125
125
  #
126
+ # On PostgreSQL 15+, the <tt>IS [NOT] JSON</tt> operator is supported:
127
+ #
128
+ # j.is_json # j IS JSON
129
+ # j.is_json(type: :object) # j IS JSON OBJECT
130
+ # j.is_json(type: :object, unique: true) # j IS JSON OBJECT WITH UNIQUE
131
+ # j.is_not_json # j IS NOT JSON
132
+ # j.is_json(type: :array) # j IS NOT JSON ARRAY
133
+ # j.is_json(unique: true) # j IS NOT JSON WITH UNIQUE
134
+ #
126
135
  # If you are also using the pg_json extension, you should load it before
127
136
  # loading this extension. Doing so will allow you to use the #op method on
128
137
  # JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
@@ -151,6 +160,18 @@ module Sequel
151
160
  GET_PATH = ["(".freeze, " #> ".freeze, ")".freeze].freeze
152
161
  GET_PATH_TEXT = ["(".freeze, " #>> ".freeze, ")".freeze].freeze
153
162
 
163
+ IS_JSON = ["(".freeze, " IS JSON".freeze, "".freeze, ")".freeze].freeze
164
+ IS_NOT_JSON = ["(".freeze, " IS NOT JSON".freeze, "".freeze, ")".freeze].freeze
165
+ EMPTY_STRING = Sequel::LiteralString.new('').freeze
166
+ WITH_UNIQUE = Sequel::LiteralString.new(' WITH UNIQUE').freeze
167
+ IS_JSON_MAP = {
168
+ nil => EMPTY_STRING,
169
+ :value => Sequel::LiteralString.new(' VALUE').freeze,
170
+ :scalar => Sequel::LiteralString.new(' SCALAR').freeze,
171
+ :object => Sequel::LiteralString.new(' OBJECT').freeze,
172
+ :array => Sequel::LiteralString.new(' ARRAY').freeze
173
+ }.freeze
174
+
154
175
  # Get JSON array element or object field as json. If an array is given,
155
176
  # gets the object at the specified path.
156
177
  #
@@ -233,6 +254,30 @@ module Sequel
233
254
  end
234
255
  end
235
256
 
257
+ # Return whether the json object can be parsed as JSON.
258
+ #
259
+ # Options:
260
+ # :type :: Check whether the json object can be parsed as a specific type
261
+ # of JSON (:value, :scalar, :object, :array).
262
+ # :unique :: Check JSON objects for unique keys.
263
+ #
264
+ # json_op.is_json # json IS JSON
265
+ # json_op.is_json(type: :object) # json IS JSON OBJECT
266
+ # json_op.is_json(unique: true) # json IS JSON WITH UNIQUE
267
+ def is_json(opts=OPTS)
268
+ _is_json(IS_JSON, opts)
269
+ end
270
+
271
+ # Return whether the json object cannot be parsed as JSON. The opposite
272
+ # of #is_json. See #is_json for options.
273
+ #
274
+ # json_op.is_not_json # json IS NOT JSON
275
+ # json_op.is_not_json(type: :object) # json IS NOT JSON OBJECT
276
+ # json_op.is_not_json(unique: true) # json IS NOT JSON WITH UNIQUE
277
+ def is_not_json(opts=OPTS)
278
+ _is_json(IS_NOT_JSON, opts)
279
+ end
280
+
236
281
  # Returns a set of keys AS text in the json object.
237
282
  #
238
283
  # json_op.keys # json_object_keys(json)
@@ -286,6 +331,13 @@ module Sequel
286
331
 
287
332
  private
288
333
 
334
+ # Internals of IS [NOT] JSON support
335
+ def _is_json(lit_array, opts)
336
+ raise Error, "invalid is_json :type option: #{opts[:type].inspect}" unless type = IS_JSON_MAP[opts[:type]]
337
+ unique = opts[:unique] ? WITH_UNIQUE : EMPTY_STRING
338
+ Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(lit_array, [self, type, unique]))
339
+ end
340
+
289
341
  # Return a placeholder literal with the given str and args, wrapped
290
342
  # in an JSONOp or JSONBOp, used by operators that return json or jsonb.
291
343
  def json_op(str, args)
@@ -306,7 +358,7 @@ module Sequel
306
358
  # Automatically wrap argument in a PGArray if it is a plain Array.
307
359
  # Requires that the pg_array extension has been loaded to work.
308
360
  def wrap_array(arg)
309
- if arg.instance_of?(Array) && Sequel.respond_to?(:pg_array)
361
+ if arg.instance_of?(Array) && defined?(Sequel.pg_array)
310
362
  Sequel.pg_array(arg)
311
363
  else
312
364
  arg
@@ -600,7 +652,7 @@ module Sequel
600
652
 
601
653
  # Wrap argument in a PGArray if it is an array
602
654
  def wrap_input_array(obj)
603
- if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
655
+ if obj.is_a?(Array) && defined?(Sequel.pg_array)
604
656
  Sequel.pg_array(obj)
605
657
  else
606
658
  obj
@@ -609,7 +661,7 @@ module Sequel
609
661
 
610
662
  # Wrap argument in a JSONBArray or JSONBHash if it is an array or hash.
611
663
  def wrap_input_jsonb(obj)
612
- if Sequel.respond_to?(:pg_jsonb) && (obj.is_a?(Array) || obj.is_a?(Hash))
664
+ if defined?(Sequel.pg_jsonb) && (obj.is_a?(Array) || obj.is_a?(Hash))
613
665
  Sequel.pg_jsonb(obj)
614
666
  else
615
667
  obj
@@ -124,7 +124,7 @@ module Sequel
124
124
  register_multirange_type('datemultirange', :range_oid=>3912, :oid=>4535)
125
125
  register_multirange_type('int8multirange', :range_oid=>3926, :oid=>4536)
126
126
 
127
- if respond_to?(:register_array_type)
127
+ if defined?(register_array_type)
128
128
  register_array_type('int4multirange', :oid=>6150, :scalar_oid=>4451, :scalar_typecast=>:int4multirange)
129
129
  register_array_type('nummultirange', :oid=>6151, :scalar_oid=>4532, :scalar_typecast=>:nummultirange)
130
130
  register_array_type('tsmultirange', :oid=>6152, :scalar_oid=>4533, :scalar_typecast=>:tsmultirange)
@@ -141,7 +141,7 @@ module Sequel
141
141
  add_conversion_proc(4533, PGMultiRange::Creator.new("tsmultirange", procs[3908]))
142
142
  add_conversion_proc(4534, PGMultiRange::Creator.new("tstzmultirange", procs[3910]))
143
143
 
144
- if respond_to?(:register_array_type) && defined?(PGArray::Creator)
144
+ if defined?(register_array_type) && defined?(PGArray::Creator)
145
145
  add_conversion_proc(6152, PGArray::Creator.new("tsmultirange", procs[4533]))
146
146
  add_conversion_proc(6153, PGArray::Creator.new("tstzmultirange", procs[4534]))
147
147
  end
@@ -139,7 +139,7 @@ module Sequel
139
139
  register_range_type('tstzrange', :oid=>3910, :subtype_oid=>1184)
140
140
  register_range_type('daterange', :oid=>3912, :subtype_oid=>1082)
141
141
  register_range_type('int8range', :oid=>3926, :subtype_oid=>20)
142
- if respond_to?(:register_array_type)
142
+ if defined?(register_array_type)
143
143
  register_array_type('int4range', :oid=>3905, :scalar_oid=>3904, :scalar_typecast=>:int4range)
144
144
  register_array_type('numrange', :oid=>3907, :scalar_oid=>3906, :scalar_typecast=>:numrange)
145
145
  register_array_type('tsrange', :oid=>3909, :scalar_oid=>3908, :scalar_typecast=>:tsrange)
@@ -154,7 +154,7 @@ module Sequel
154
154
  procs = conversion_procs
155
155
  add_conversion_proc(3908, Parser.new("tsrange", procs[1114]))
156
156
  add_conversion_proc(3910, Parser.new("tstzrange", procs[1184]))
157
- if respond_to?(:register_array_type) && defined?(PGArray::Creator)
157
+ if defined?(register_array_type) && defined?(PGArray::Creator)
158
158
  add_conversion_proc(3909, PGArray::Creator.new("tsrange", procs[3908]))
159
159
  add_conversion_proc(3911, PGArray::Creator.new("tstzrange", procs[3910]))
160
160
  end
@@ -375,7 +375,7 @@ module Sequel
375
375
  @row_schema_types = {}
376
376
  extend(@row_type_method_module = Module.new)
377
377
  add_conversion_proc(2249, PGRow::Parser.new(:converter=>PGRow::ArrayRow))
378
- if respond_to?(:register_array_type)
378
+ if defined?(register_array_type)
379
379
  register_array_type('record', :oid=>2287, :scalar_oid=>2249)
380
380
  end
381
381
  end
@@ -464,7 +464,7 @@ module Sequel
464
464
  parser = Parser.new(parser_opts)
465
465
  add_conversion_proc(parser.oid, parser)
466
466
 
467
- if respond_to?(:register_array_type) && array_oid && array_oid > 0
467
+ if defined?(register_array_type) && array_oid && array_oid > 0
468
468
  array_type_name = if type_schema
469
469
  "#{type_schema}.#{type_name}"
470
470
  else
@@ -115,13 +115,13 @@ SQL
115
115
  # :before_thread_exit :: An object that responds to +call+ that is called before the
116
116
  # the created thread exits.
117
117
  def listen_for_static_cache_updates(models, opts=OPTS)
118
- raise Error, "this database object does not respond to listen, use the postgres adapter with the pg driver" unless respond_to?(:listen)
118
+ raise Error, "this database object does not respond to listen, use the postgres adapter with the pg driver" unless defined?(listen)
119
119
  models = [models] unless models.is_a?(Array)
120
120
  raise Error, "array of models to listen for changes cannot be empty" if models.empty?
121
121
 
122
122
  oid_map = {}
123
123
  models.each do |model|
124
- raise Error, "#{model.inspect} does not use the static_cache plugin" unless model.respond_to?(:load_cache)
124
+ raise Error, "#{model.inspect} does not use the static_cache plugin" unless defined?(model.load_cache)
125
125
  oid_map[get(regclass_oid(model.dataset.first_source_table))] = model
126
126
  end
127
127
 
@@ -35,6 +35,7 @@ if RUBY_VERSION >= '2.0'
35
35
  class Symbol
36
36
  prepend Sequel::SymbolAref
37
37
  end
38
+ # :nocov:
38
39
  else
39
40
  class Symbol
40
41
  if method_defined?(:[])
@@ -51,3 +52,4 @@ else
51
52
  end
52
53
  end
53
54
  end
55
+ # :nocov:
@@ -1717,6 +1717,8 @@ module Sequel
1717
1717
  # :graph_select :: A column or array of columns to select from the associated table
1718
1718
  # when eagerly loading the association via +eager_graph+. Defaults to all
1719
1719
  # columns in the associated table.
1720
+ # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1721
+ # uses instance specific state, or transient state (accessing current date/time, etc.).
1720
1722
  # :limit :: Limit the number of records to the provided value. Use
1721
1723
  # an array with two elements for the value to specify a
1722
1724
  # limit (first element) and an offset (second element).
@@ -1856,6 +1858,16 @@ module Sequel
1856
1858
  # in certain places to disable optimizations.
1857
1859
  opts[:instance_specific] = _association_instance_specific_default(name)
1858
1860
  end
1861
+ if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
1862
+ # For associations explicitly marked as instance specific, or that use the
1863
+ # :dataset option, where :allow_eager is not set, and no :eager_loader is
1864
+ # provided, disallow eager loading. In these cases, eager loading is
1865
+ # unlikely to work. This is not done for implicit setting of :instance_specific,
1866
+ # because implicit use is done by default for all associations with blocks,
1867
+ # and the vast majority of associations with blocks use the block for filtering
1868
+ # in a manner compatible with eager loading.
1869
+ opts[:allow_eager] = false
1870
+ end
1859
1871
  opts = assoc_class.new.merge!(opts)
1860
1872
 
1861
1873
  if opts[:clone] && !opts.cloneable?(cloned_assoc)
@@ -3004,7 +3016,7 @@ module Sequel
3004
3016
  def complex_expression_sql_append(sql, op, args)
3005
3017
  r = args[1]
3006
3018
  if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
3007
- (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
3019
+ (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (defined?(r.all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
3008
3020
  l = args[0]
3009
3021
  if ar = model.association_reflections[l]
3010
3022
  raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
@@ -3012,7 +3024,7 @@ module Sequel
3012
3024
  if multiple
3013
3025
  klass = ar.associated_class
3014
3026
  if is_ds
3015
- if r.respond_to?(:model)
3027
+ if defined?(r.model)
3016
3028
  unless r.model <= klass
3017
3029
  # A dataset for a different model class, could be a valid regular query
3018
3030
  return super
@@ -3344,10 +3356,10 @@ module Sequel
3344
3356
  assoc_table_alias = ds.unused_table_alias(alias_base)
3345
3357
  loader = r[:eager_grapher]
3346
3358
  if !associations.empty?
3347
- if associations.first.respond_to?(:call)
3359
+ if defined?(associations.first.call)
3348
3360
  callback = associations.first
3349
3361
  associations = {}
3350
- elsif associations.length == 1 && (assocs = associations.first).is_a?(Hash) && assocs.length == 1 && (pr_assoc = assocs.to_a.first) && pr_assoc.first.respond_to?(:call)
3362
+ elsif associations.length == 1 && (assocs = associations.first).is_a?(Hash) && assocs.length == 1 && (pr_assoc = assocs.to_a.first) && defined?(pr_assoc.first.call)
3351
3363
  callback, assoc = pr_assoc
3352
3364
  associations = assoc.is_a?(Array) ? assoc : [assoc]
3353
3365
  end
@@ -3589,10 +3601,10 @@ module Sequel
3589
3601
  end
3590
3602
 
3591
3603
  associations = eager_assoc[r[:name]]
3592
- if associations.respond_to?(:call)
3604
+ if defined?(associations.call)
3593
3605
  eager_block = associations
3594
3606
  associations = OPTS
3595
- elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3607
+ elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && defined?(pr_assoc.first.call)
3596
3608
  eager_block, associations = pr_assoc
3597
3609
  end
3598
3610
 
@@ -492,13 +492,13 @@ module Sequel
492
492
  def plugin(plugin, *args, &block)
493
493
  m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
494
494
 
495
- if !m.respond_to?(:apply) && !m.respond_to?(:configure) && (!args.empty? || block)
495
+ if !defined?(m.apply) && !defined?(m.configure) && (!args.empty? || block)
496
496
  Deprecation.deprecate("Plugin #{plugin} accepts no arguments or block, and passing arguments/block to it", "Remove arguments and block when loading the plugin")
497
497
  end
498
498
 
499
499
  unless @plugins.include?(m)
500
500
  @plugins << m
501
- m.apply(self, *args, &block) if m.respond_to?(:apply)
501
+ m.apply(self, *args, &block) if defined?(m.apply)
502
502
  extend(m::ClassMethods) if m.const_defined?(:ClassMethods, false)
503
503
  include(m::InstanceMethods) if m.const_defined?(:InstanceMethods, false)
504
504
  if m.const_defined?(:DatasetMethods, false)
@@ -506,7 +506,7 @@ module Sequel
506
506
  end
507
507
  end
508
508
 
509
- m.configure(self, *args, &block) if m.respond_to?(:configure)
509
+ m.configure(self, *args, &block) if defined?(m.configure)
510
510
  end
511
511
  # :nocov:
512
512
  ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
@@ -680,10 +680,11 @@ module Sequel
680
680
 
681
681
  private
682
682
 
683
- # Yield to the passed block and if do_raise is false, swallow all errors other than DatabaseConnectionErrors.
683
+ # Yield to the passed block and if do_raise is false, swallow Sequel::Errors other than DatabaseConnectionError
684
+ # and DatabaseDisconnectError.
684
685
  def check_non_connection_error(do_raise=require_valid_table)
685
686
  db.transaction(:savepoint=>:only){yield}
686
- rescue Sequel::DatabaseConnectionError
687
+ rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
687
688
  raise
688
689
  rescue Sequel::Error
689
690
  raise if do_raise
@@ -693,9 +694,12 @@ module Sequel
693
694
  # this model's dataset.
694
695
  def convert_input_dataset(ds)
695
696
  case ds
696
- when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
697
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
697
698
  self.simple_table = db.literal(ds).freeze
698
699
  ds = db.from(ds)
700
+ when SQL::AliasedExpression, LiteralString
701
+ self.simple_table = nil
702
+ ds = db.from(ds)
699
703
  when Dataset
700
704
  ds = ds.from_self(:alias=>ds.first_source) if ds.joined_dataset?
701
705
 
@@ -784,7 +788,7 @@ module Sequel
784
788
  schema_hash = {}
785
789
  ds_opts = dataset.opts
786
790
  get_columns = proc{check_non_connection_error{columns} || []}
787
- schema_array = check_non_connection_error(false){db.schema(dataset, :reload=>reload)} if db.supports_schema_parsing?
791
+ schema_array = get_db_schema_array(reload) if db.supports_schema_parsing?
788
792
  if schema_array
789
793
  schema_array.each{|k,v| schema_hash[k] = v}
790
794
 
@@ -821,6 +825,12 @@ module Sequel
821
825
  schema_hash
822
826
  end
823
827
 
828
+ # Get the array of schema information for the dataset. Returns nil if
829
+ # the schema information cannot be determined.
830
+ def get_db_schema_array(reload)
831
+ check_non_connection_error(false){db.schema(dataset, :reload=>reload)}
832
+ end
833
+
824
834
  # Uncached version of setter_methods, to be overridden by plugins
825
835
  # that want to modify the methods used.
826
836
  def get_setter_methods
@@ -44,7 +44,7 @@ module Sequel
44
44
  errors = @model.errors
45
45
  end
46
46
 
47
- if errors.respond_to?(:full_messages)
47
+ if defined?(errors.full_messages)
48
48
  @errors = errors
49
49
  super(errors.full_messages.join(', '))
50
50
  else
@@ -99,7 +99,7 @@ module Sequel
99
99
  # Convert the given string to CamelCase. Will also convert '/' to '::' which is useful for converting paths to namespaces.
100
100
  def camelize(s)
101
101
  s = s.to_s
102
- return s.camelize if s.respond_to?(:camelize)
102
+ return s.camelize if defined?(s.camelize)
103
103
  s = s.gsub(/\/(.?)/){|x| "::#{x[-1..-1].upcase unless x == '/'}"}.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
104
104
  s
105
105
  end
@@ -109,7 +109,7 @@ module Sequel
109
109
  # or is not initialized.
110
110
  def constantize(s)
111
111
  s = s.to_s
112
- return s.constantize if s.respond_to?(:constantize)
112
+ return s.constantize if defined?(s.constantize)
113
113
  raise(NameError, "#{s.inspect} is not a valid constant name!") unless m = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.match(s)
114
114
  Object.module_eval("::#{m[1]}", __FILE__, __LINE__)
115
115
  end
@@ -117,14 +117,14 @@ module Sequel
117
117
  # Removes the module part from the expression in the string
118
118
  def demodulize(s)
119
119
  s = s.to_s
120
- return s.demodulize if s.respond_to?(:demodulize)
120
+ return s.demodulize if defined?(s.demodulize)
121
121
  s.gsub(/^.*::/, '')
122
122
  end
123
123
 
124
124
  # Returns the plural form of the word in the string.
125
125
  def pluralize(s)
126
126
  s = s.to_s
127
- return s.pluralize if s.respond_to?(:pluralize)
127
+ return s.pluralize if defined?(s.pluralize)
128
128
  result = s.dup
129
129
  Inflections.plurals.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase)
130
130
  result
@@ -133,7 +133,7 @@ module Sequel
133
133
  # The reverse of pluralize, returns the singular form of a word in a string.
134
134
  def singularize(s)
135
135
  s = s.to_s
136
- return s.singularize if s.respond_to?(:singularize)
136
+ return s.singularize if defined?(s.singularize)
137
137
  result = s.dup
138
138
  Inflections.singulars.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase)
139
139
  result
@@ -143,7 +143,7 @@ module Sequel
143
143
  # Also changes '::' to '/' to convert namespaces to paths.
144
144
  def underscore(s)
145
145
  s = s.to_s
146
- return s.underscore if s.respond_to?(:underscore)
146
+ return s.underscore if defined?(s.underscore)
147
147
  s.gsub('::', '/').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
148
148
  gsub(/([a-z\d])([A-Z])/, '\1_\2').tr('-', '_').downcase
149
149
  end
@@ -252,7 +252,7 @@ module Sequel
252
252
 
253
253
  unless skip.include?(:unique)
254
254
  unique_opts = Hash[opts[:unique]]
255
- if model.respond_to?(:sti_dataset)
255
+ if defined?(model.sti_dataset)
256
256
  unique_opts[:dataset] = model.sti_dataset
257
257
  end
258
258
  model.auto_validate_unique_columns.each{|cols| validates_unique(cols, unique_opts)}
@@ -117,7 +117,7 @@ module Sequel
117
117
  def [](k)
118
118
  if new? && !values.has_key?(k)
119
119
  v = model.default_values.fetch(k){return}
120
- v = v.call if v.respond_to?(:call)
120
+ v = v.call if defined?(v.call)
121
121
  values[k] = v if model.cache_default_values?
122
122
  v
123
123
  else
@@ -203,7 +203,7 @@ module Sequel
203
203
  get_column_value(column)
204
204
  end
205
205
 
206
- initial_values[column] = if value && value != true && value.respond_to?(:clone)
206
+ initial_values[column] = if value && value != true && defined?(value.clone)
207
207
  begin
208
208
  value.clone
209
209
  rescue TypeError
@@ -36,7 +36,7 @@ module Sequel
36
36
  module InsertConflict
37
37
  def self.configure(model)
38
38
  model.instance_exec do
39
- if @dataset && !@dataset.respond_to?(:insert_conflict)
39
+ if @dataset && !defined?(@dataset.insert_conflict)
40
40
  raise Error, "#{self}'s dataset does not support insert_conflict"
41
41
  end
42
42
  end