sequel 5.39.0 → 5.62.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +294 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +89 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/schema_modification.rdoc +1 -1
  39. data/doc/security.rdoc +9 -9
  40. data/doc/sql.rdoc +27 -15
  41. data/doc/testing.rdoc +22 -11
  42. data/doc/transactions.rdoc +6 -6
  43. data/doc/virtual_rows.rdoc +2 -2
  44. data/lib/sequel/adapters/ado/access.rb +1 -1
  45. data/lib/sequel/adapters/ado.rb +17 -17
  46. data/lib/sequel/adapters/amalgalite.rb +3 -5
  47. data/lib/sequel/adapters/ibmdb.rb +2 -2
  48. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  49. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  50. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  51. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  52. data/lib/sequel/adapters/jdbc.rb +16 -18
  53. data/lib/sequel/adapters/mysql.rb +80 -67
  54. data/lib/sequel/adapters/mysql2.rb +54 -49
  55. data/lib/sequel/adapters/odbc.rb +6 -2
  56. data/lib/sequel/adapters/oracle.rb +3 -3
  57. data/lib/sequel/adapters/postgres.rb +83 -40
  58. data/lib/sequel/adapters/shared/access.rb +11 -1
  59. data/lib/sequel/adapters/shared/db2.rb +30 -0
  60. data/lib/sequel/adapters/shared/mssql.rb +58 -7
  61. data/lib/sequel/adapters/shared/mysql.rb +40 -2
  62. data/lib/sequel/adapters/shared/oracle.rb +76 -0
  63. data/lib/sequel/adapters/shared/postgres.rb +418 -174
  64. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
  65. data/lib/sequel/adapters/shared/sqlite.rb +102 -11
  66. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  67. data/lib/sequel/adapters/sqlite.rb +60 -18
  68. data/lib/sequel/adapters/tinytds.rb +1 -1
  69. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  70. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  71. data/lib/sequel/ast_transformer.rb +6 -0
  72. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  73. data/lib/sequel/connection_pool/single.rb +6 -8
  74. data/lib/sequel/connection_pool.rb +42 -28
  75. data/lib/sequel/core.rb +28 -18
  76. data/lib/sequel/database/connecting.rb +26 -2
  77. data/lib/sequel/database/misc.rb +69 -14
  78. data/lib/sequel/database/query.rb +38 -1
  79. data/lib/sequel/database/schema_generator.rb +45 -52
  80. data/lib/sequel/database/schema_methods.rb +17 -1
  81. data/lib/sequel/dataset/actions.rb +82 -13
  82. data/lib/sequel/dataset/features.rb +20 -0
  83. data/lib/sequel/dataset/misc.rb +1 -1
  84. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  85. data/lib/sequel/dataset/query.rb +118 -16
  86. data/lib/sequel/dataset/sql.rb +177 -47
  87. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  88. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  89. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  90. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  91. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  92. data/lib/sequel/extensions/blank.rb +8 -0
  93. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  94. data/lib/sequel/extensions/core_refinements.rb +36 -11
  95. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  96. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  97. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  98. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  99. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  100. data/lib/sequel/extensions/inflector.rb +9 -1
  101. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  103. data/lib/sequel/extensions/migration.rb +7 -2
  104. data/lib/sequel/extensions/named_timezones.rb +22 -6
  105. data/lib/sequel/extensions/pagination.rb +1 -1
  106. data/lib/sequel/extensions/pg_array.rb +23 -3
  107. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  108. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  109. data/lib/sequel/extensions/pg_enum.rb +1 -1
  110. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  111. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  112. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  113. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  114. data/lib/sequel/extensions/pg_inet.rb +10 -11
  115. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  116. data/lib/sequel/extensions/pg_interval.rb +45 -19
  117. data/lib/sequel/extensions/pg_json.rb +13 -15
  118. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  119. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  120. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  121. data/lib/sequel/extensions/pg_range.rb +10 -23
  122. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  123. data/lib/sequel/extensions/pg_row.rb +19 -13
  124. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  125. data/lib/sequel/extensions/query.rb +2 -0
  126. data/lib/sequel/extensions/s.rb +2 -1
  127. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  128. data/lib/sequel/extensions/server_block.rb +8 -12
  129. data/lib/sequel/extensions/sql_comments.rb +110 -3
  130. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  131. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  132. data/lib/sequel/extensions/string_agg.rb +1 -1
  133. data/lib/sequel/extensions/string_date_time.rb +19 -23
  134. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  135. data/lib/sequel/model/associations.rb +324 -95
  136. data/lib/sequel/model/base.rb +51 -27
  137. data/lib/sequel/model/errors.rb +10 -1
  138. data/lib/sequel/model/inflections.rb +1 -1
  139. data/lib/sequel/model/plugins.rb +5 -0
  140. data/lib/sequel/plugins/association_proxies.rb +2 -0
  141. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  142. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  143. data/lib/sequel/plugins/auto_validations.rb +87 -15
  144. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  145. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  146. data/lib/sequel/plugins/column_encryption.rb +728 -0
  147. data/lib/sequel/plugins/composition.rb +10 -4
  148. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  149. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  150. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  151. data/lib/sequel/plugins/dirty.rb +1 -1
  152. data/lib/sequel/plugins/enum.rb +124 -0
  153. data/lib/sequel/plugins/finder.rb +3 -1
  154. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  155. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  156. data/lib/sequel/plugins/json_serializer.rb +39 -24
  157. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  158. data/lib/sequel/plugins/list.rb +3 -1
  159. data/lib/sequel/plugins/many_through_many.rb +108 -9
  160. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  161. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  162. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  163. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  164. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  165. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  166. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  167. data/lib/sequel/plugins/serialization.rb +9 -3
  168. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  169. data/lib/sequel/plugins/sql_comments.rb +189 -0
  170. data/lib/sequel/plugins/static_cache.rb +1 -1
  171. data/lib/sequel/plugins/subclasses.rb +28 -11
  172. data/lib/sequel/plugins/tactical_eager_loading.rb +15 -2
  173. data/lib/sequel/plugins/timestamps.rb +1 -1
  174. data/lib/sequel/plugins/unused_associations.rb +521 -0
  175. data/lib/sequel/plugins/update_or_create.rb +1 -1
  176. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  177. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  178. data/lib/sequel/sql.rb +1 -1
  179. data/lib/sequel/timezones.rb +12 -14
  180. data/lib/sequel/version.rb +1 -1
  181. metadata +94 -43
@@ -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
  #
@@ -280,6 +280,11 @@ module Sequel
280
280
  str
281
281
  end
282
282
 
283
+ # Allow automatic parameterization.
284
+ def sequel_auto_param_type(ds)
285
+ "::hstore"
286
+ end
287
+
283
288
  private
284
289
 
285
290
  # Return a new hash based on the input hash with string
@@ -62,6 +62,19 @@
62
62
  # # Delete a key
63
63
  # DB[:tab].update(h: Sequel.hstore_op(:h).delete('k1'))
64
64
  #
65
+ # On PostgreSQL 14+, The hstore <tt>[]</tt> method will use subscripts instead of being
66
+ # the same as +get+, if the value being wrapped is an identifer:
67
+ #
68
+ # Sequel.hstore_op(:hstore_column)['a'] # hstore_column['a']
69
+ # Sequel.hstore_op(Sequel[:h][:s])['a'] # h.s['a']
70
+ #
71
+ # This support allows you to use hstore subscripts in UPDATE statements to update only
72
+ # part of a column:
73
+ #
74
+ # h = Sequel.hstore_op(:h)
75
+ # DB[:t].update(h['key1'] => 'val1', h['key2'] => 'val2')
76
+ # # UPDATE "t" SET "h"['key1'] = 'val1', "h"['key2'] = 'val2'
77
+ #
65
78
  # See the PostgreSQL hstore function and operator documentation for more
66
79
  # details on what these functions and operators do.
67
80
  #
@@ -114,10 +127,15 @@ module Sequel
114
127
  #
115
128
  # hstore_op['a'] # (hstore -> 'a')
116
129
  def [](key)
117
- v = Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, wrap_input_array(key)])
118
130
  if key.is_a?(Array) || (defined?(Sequel::Postgres::PGArray) && key.is_a?(Sequel::Postgres::PGArray)) || (defined?(Sequel::Postgres::ArrayOp) && key.is_a?(Sequel::Postgres::ArrayOp))
119
- wrap_output_array(v)
131
+ wrap_output_array(Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, wrap_input_array(key)]))
120
132
  else
133
+ v = case @value
134
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
135
+ HStoreSubscriptOp.new(self, key)
136
+ else
137
+ Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, key])
138
+ end
121
139
  Sequel::SQL::StringExpression.new(:NOOP, v)
122
140
  end
123
141
  end
@@ -304,6 +322,38 @@ module Sequel
304
322
  end
305
323
  end
306
324
 
325
+ # Represents hstore subscripts. This is abstracted because the
326
+ # subscript support depends on the database version.
327
+ class HStoreSubscriptOp < SQL::Expression
328
+ SUBSCRIPT = ["".freeze, "[".freeze, "]".freeze].freeze
329
+
330
+ # The expression being subscripted
331
+ attr_reader :expression
332
+
333
+ # The subscript to use
334
+ attr_reader :sub
335
+
336
+ # Set the expression and subscript to the given arguments
337
+ def initialize(expression, sub)
338
+ @expression = expression
339
+ @sub = sub
340
+ freeze
341
+ end
342
+
343
+ # Use subscripts instead of -> operator on PostgreSQL 14+
344
+ def to_s_append(ds, sql)
345
+ server_version = ds.db.server_version
346
+ frag = server_version && server_version >= 140000 ? SUBSCRIPT : HStoreOp::LOOKUP
347
+ ds.literal_append(sql, Sequel::SQL::PlaceholderLiteralString.new(frag, [@expression, @sub]))
348
+ end
349
+
350
+ # Support transforming of hstore subscripts
351
+ def sequel_ast_transform(transformer)
352
+ self.class.new(transformer.call(@expression), transformer.call(@sub))
353
+ end
354
+ end
355
+
356
+
307
357
  module HStoreOpMethods
308
358
  # Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
309
359
  # hstore functions and operators with it.
@@ -356,7 +406,7 @@ end
356
406
  if defined?(Sequel::CoreRefinements)
357
407
  module Sequel::CoreRefinements
358
408
  refine Symbol do
359
- include Sequel::Postgres::HStoreOpMethods
409
+ send INCLUDE_METH, Sequel::Postgres::HStoreOpMethods
360
410
  end
361
411
  end
362
412
  end
@@ -75,16 +75,6 @@ module Sequel
75
75
 
76
76
  private
77
77
 
78
- # Handle inet[]/cidr[] types in bound variables.
79
- def bound_variable_array(a)
80
- case a
81
- when IPAddr
82
- "\"#{a.to_s}/#{a.instance_variable_get(:@mask_addr).to_s(2).count('1')}\""
83
- else
84
- super
85
- end
86
- end
87
-
88
78
  # Make the column type detection recognize the inet and cidr types.
89
79
  def schema_column_type(db_type)
90
80
  case db_type
@@ -111,7 +101,7 @@ module Sequel
111
101
  when IPAddr
112
102
  value
113
103
  when String
114
- IPAddr.new(value)
104
+ IPAddr.new(typecast_check_string_length(value, 100))
115
105
  else
116
106
  raise Sequel::InvalidValue, "invalid value for inet/cidr: #{value.inspect}"
117
107
  end
@@ -121,6 +111,15 @@ module Sequel
121
111
  module InetDatasetMethods
122
112
  private
123
113
 
114
+ # Allow auto parameterization of IPAddr instances.
115
+ def auto_param_type_fallback(v)
116
+ if defined?(super) && (type = super)
117
+ type
118
+ elsif IPAddr === v
119
+ "::inet"
120
+ end
121
+ end
122
+
124
123
  # Convert IPAddr value to a string and append a literal version
125
124
  # of the string to the sql.
126
125
  def literal_other_append(sql, value)
@@ -197,7 +197,7 @@ end
197
197
  if defined?(Sequel::CoreRefinements)
198
198
  module Sequel::CoreRefinements
199
199
  refine Symbol do
200
- include Sequel::Postgres::InetOpMethods
200
+ send INCLUDE_METH, Sequel::Postgres::InetOpMethods
201
201
  end
202
202
  end
203
203
  end
@@ -32,8 +32,16 @@
32
32
  #
33
33
  # Related module: Sequel::Postgres::IntervalDatabaseMethods
34
34
 
35
+ require 'active_support'
35
36
  require 'active_support/duration'
36
37
 
38
+ # :nocov:
39
+ begin
40
+ require 'active_support/version'
41
+ rescue LoadError
42
+ end
43
+ # :nocov:
44
+
37
45
  module Sequel
38
46
  module Postgres
39
47
  module IntervalDatabaseMethods
@@ -61,34 +69,47 @@ module Sequel
61
69
 
62
70
  # Creates callable objects that convert strings into ActiveSupport::Duration instances.
63
71
  class Parser
72
+ # Whether ActiveSupport::Duration.new takes parts as array instead of hash
73
+ USE_PARTS_ARRAY = !defined?(ActiveSupport::VERSION::STRING) || ActiveSupport::VERSION::STRING < '5.1'
74
+
75
+ if defined?(ActiveSupport::Duration::SECONDS_PER_MONTH)
76
+ SECONDS_PER_MONTH = ActiveSupport::Duration::SECONDS_PER_MONTH
77
+ SECONDS_PER_YEAR = ActiveSupport::Duration::SECONDS_PER_YEAR
78
+ # :nocov:
79
+ else
80
+ SECONDS_PER_MONTH = 2592000
81
+ SECONDS_PER_YEAR = 31557600
82
+ # :nocov:
83
+ end
84
+
64
85
  # Parse the interval input string into an ActiveSupport::Duration instance.
65
86
  def call(string)
66
87
  raise(InvalidValue, "invalid or unhandled interval format: #{string.inspect}") unless matches = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:(?:([+-])?(\d{2,10}):(\d\d):(\d\d(\.\d+)?))|([+-]?\d+ hours?\s?)?([+-]?\d+ mins?\s?)?([+-]?\d+(\.\d+)? secs?\s?)?)?\z/.match(string)
67
88
 
68
89
  value = 0
69
- parts = []
90
+ parts = {}
70
91
 
71
92
  if v = matches[1]
72
93
  v = v.to_i
73
- value += 31557600 * v
74
- parts << [:years, v]
94
+ value += SECONDS_PER_YEAR * v
95
+ parts[:years] = v
75
96
  end
76
97
  if v = matches[2]
77
98
  v = v.to_i
78
- value += 2592000 * v
79
- parts << [:months, v]
99
+ value += SECONDS_PER_MONTH * v
100
+ parts[:months] = v
80
101
  end
81
102
  if v = matches[3]
82
103
  v = v.to_i
83
104
  value += 86400 * v
84
- parts << [:days, v]
105
+ parts[:days] = v
85
106
  end
86
107
  if matches[5]
87
108
  seconds = matches[5].to_i * 3600 + matches[6].to_i * 60
88
109
  seconds += matches[8] ? matches[7].to_f : matches[7].to_i
89
110
  seconds *= -1 if matches[4] == '-'
90
111
  value += seconds
91
- parts << [:seconds, seconds]
112
+ parts[:seconds] = seconds
92
113
  elsif matches[9] || matches[10] || matches[11]
93
114
  seconds = 0
94
115
  if v = matches[9]
@@ -101,8 +122,14 @@ module Sequel
101
122
  seconds += matches[12] ? v.to_f : v.to_i
102
123
  end
103
124
  value += seconds
104
- parts << [:seconds, seconds]
125
+ parts[:seconds] = seconds
126
+ end
127
+
128
+ # :nocov:
129
+ if USE_PARTS_ARRAY
130
+ parts = parts.to_a
105
131
  end
132
+ # :nocov:
106
133
 
107
134
  ActiveSupport::Duration.new(value, parts)
108
135
  end
@@ -136,16 +163,6 @@ module Sequel
136
163
 
137
164
  private
138
165
 
139
- # Handle arrays of interval types in bound variables.
140
- def bound_variable_array(a)
141
- case a
142
- when ActiveSupport::Duration
143
- "\"#{IntervalDatabaseMethods.literal_duration(a)}\""
144
- else
145
- super
146
- end
147
- end
148
-
149
166
  # Set the :ruby_default value if the default value is recognized as an interval.
150
167
  def schema_post_process(_)
151
168
  super.each do |a|
@@ -170,7 +187,7 @@ module Sequel
170
187
  when Numeric
171
188
  ActiveSupport::Duration.new(value, [[:seconds, value]])
172
189
  when String
173
- PARSER.call(value)
190
+ PARSER.call(typecast_check_string_length(value, 1000))
174
191
  else
175
192
  raise Sequel::InvalidValue, "invalid value for interval type: #{value.inspect}"
176
193
  end
@@ -180,6 +197,15 @@ module Sequel
180
197
  module IntervalDatasetMethods
181
198
  private
182
199
 
200
+ # Allow auto parameterization of ActiveSupport::Duration instances.
201
+ def auto_param_type_fallback(v)
202
+ if defined?(super) && (type = super)
203
+ type
204
+ elsif ActiveSupport::Duration === v
205
+ "::interval"
206
+ end
207
+ end
208
+
183
209
  # Handle literalization of ActiveSupport::Duration objects, treating them as
184
210
  # PostgreSQL intervals.
185
211
  def literal_other_append(sql, v)
@@ -142,6 +142,11 @@ module Sequel
142
142
  ds.literal_append(sql, Sequel.object_to_json(self))
143
143
  sql << '::json'
144
144
  end
145
+
146
+ # Allow automatic parameterization.
147
+ def sequel_auto_param_type(ds)
148
+ "::json"
149
+ end
145
150
  end
146
151
 
147
152
  jsonb_class = Class.new(base_class) do
@@ -151,6 +156,11 @@ module Sequel
151
156
  ds.literal_append(sql, Sequel.object_to_json(self))
152
157
  sql << '::jsonb'
153
158
  end
159
+
160
+ # Allow automatic parameterization.
161
+ def sequel_auto_param_type(ds)
162
+ "::jsonb"
163
+ end
154
164
  end
155
165
 
156
166
  const_set(:"JSON#{name}Base", base_class)
@@ -387,11 +397,9 @@ module Sequel
387
397
  # argument is true), or a String, Numeric, true, false, or nil
388
398
  # if the json library used supports that.
389
399
  def _parse_json(s)
390
- begin
391
- Sequel.parse_json(s)
392
- rescue Sequel.json_parser_error_class => e
393
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
394
- end
400
+ Sequel.parse_json(s)
401
+ rescue Sequel.json_parser_error_class => e
402
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
395
403
  end
396
404
 
397
405
  # Wrap the parsed JSON value in the appropriate JSON wrapper class.
@@ -426,16 +434,6 @@ module Sequel
426
434
  end
427
435
  end
428
436
 
429
- # Handle json[] and jsonb[] types in bound variables.
430
- def bound_variable_array(a)
431
- case a
432
- when JSONObject, JSONBObject
433
- "\"#{Sequel.object_to_json(a).gsub('"', '\\"')}\""
434
- else
435
- super
436
- end
437
- end
438
-
439
437
  # Make the column type detection recognize the json types.
440
438
  def schema_column_type(db_type)
441
439
  case db_type
@@ -3,7 +3,8 @@
3
3
  # The pg_json_ops extension adds support to Sequel's DSL to make
4
4
  # it easier to call PostgreSQL JSON functions and operators (added
5
5
  # first in PostgreSQL 9.3). It also supports the JSONB functions
6
- # and operators added in PostgreSQL 9.4.
6
+ # and operators added in PostgreSQL 9.4, as well as additional
7
+ # functions and operators added in later versions.
7
8
  #
8
9
  # To load the extension:
9
10
  #
@@ -101,6 +102,27 @@
101
102
  # substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
102
103
  # errors are not suppressed.
103
104
  #
105
+ # On PostgreSQL 14+, The JSONB <tt>[]</tt> method will use subscripts instead of being
106
+ # the same as +get+, if the value being wrapped is an identifer:
107
+ #
108
+ # Sequel.pg_jsonb_op(:jsonb_column)[1] # jsonb_column[1]
109
+ # Sequel.pg_jsonb_op(:jsonb_column)[1][2] # jsonb_column[1][2]
110
+ # Sequel.pg_jsonb_op(Sequel[:j][:b])[1] # j.b[1]
111
+ #
112
+ # This support allows you to use JSONB subscripts in UPDATE statements to update only
113
+ # part of a column:
114
+ #
115
+ # c = Sequel.pg_jsonb_op(:c)
116
+ # DB[:t].update(c['key1'] => '1', c['key2'] => '"a"')
117
+ # # UPDATE "t" SET "c"['key1'] = '1', "c"['key2'] = '"a"'
118
+ #
119
+ # Note that you have to provide the value of a JSONB subscript as a JSONB value, so this
120
+ # will update +key1+ to use the number <tt>1</tt>, and +key2+ to use the string <tt>a</tt>.
121
+ # For this reason it may be simpler to use +to_json+:
122
+ #
123
+ # c = Sequel.pg_jsonb_op(:c)
124
+ # DB[:t].update(c['key1'] => 1.to_json, c['key2'] => "a".to_json)
125
+ #
104
126
  # If you are also using the pg_json extension, you should load it before
105
127
  # loading this extension. Doing so will allow you to use the #op method on
106
128
  # JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
@@ -323,6 +345,24 @@ module Sequel
323
345
  PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
324
346
  PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
325
347
 
348
+ # Support subscript syntax for JSONB.
349
+ def [](key)
350
+ if is_array?(key)
351
+ super
352
+ else
353
+ case @value
354
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, JSONBSubscriptOp
355
+ # Only use subscripts for identifiers. In other cases, switching from
356
+ # the -> operator to [] for subscripts causes SQL syntax issues. You
357
+ # only need the [] for subscripting when doing assignment, and
358
+ # assignment is generally done on identifiers.
359
+ self.class.new(JSONBSubscriptOp.new(self, key))
360
+ else
361
+ super
362
+ end
363
+ end
364
+ end
365
+
326
366
  # jsonb expression for deletion of the given argument from the
327
367
  # current jsonb.
328
368
  #
@@ -582,6 +622,37 @@ module Sequel
582
622
  end
583
623
  end
584
624
 
625
+ # Represents JSONB subscripts. This is abstracted because the
626
+ # subscript support depends on the database version.
627
+ class JSONBSubscriptOp < SQL::Expression
628
+ SUBSCRIPT = ["".freeze, "[".freeze, "]".freeze].freeze
629
+
630
+ # The expression being subscripted
631
+ attr_reader :expression
632
+
633
+ # The subscript to use
634
+ attr_reader :sub
635
+
636
+ # Set the expression and subscript to the given arguments
637
+ def initialize(expression, sub)
638
+ @expression = expression
639
+ @sub = sub
640
+ freeze
641
+ end
642
+
643
+ # Use subscripts instead of -> operator on PostgreSQL 14+
644
+ def to_s_append(ds, sql)
645
+ server_version = ds.db.server_version
646
+ frag = server_version && server_version >= 140000 ? SUBSCRIPT : JSONOp::GET
647
+ ds.literal_append(sql, Sequel::SQL::PlaceholderLiteralString.new(frag, [@expression, @sub]))
648
+ end
649
+
650
+ # Support transforming of jsonb subscripts
651
+ def sequel_ast_transform(transformer)
652
+ self.class.new(transformer.call(@expression), transformer.call(@sub))
653
+ end
654
+ end
655
+
585
656
  module JSONOpMethods
586
657
  # Wrap the receiver in an JSONOp so you can easily use the PostgreSQL
587
658
  # json functions and operators with it.
@@ -674,7 +745,7 @@ end
674
745
  if defined?(Sequel::CoreRefinements)
675
746
  module Sequel::CoreRefinements
676
747
  refine Symbol do
677
- include Sequel::Postgres::JSONOpMethods
748
+ send INCLUDE_METH, Sequel::Postgres::JSONOpMethods
678
749
  end
679
750
  end
680
751
  end
@@ -12,7 +12,9 @@
12
12
  #
13
13
  # How accurate this count is depends on the number of rows
14
14
  # added/deleted from the table since the last time it was
15
- # analyzed.
15
+ # analyzed. If the table has not been vacuumed or analyzed
16
+ # yet, this can return 0 or -1 depending on the PostgreSQL
17
+ # version in use.
16
18
  #
17
19
  # To load the extension into the database:
18
20
  #