sequel 5.39.0 → 5.72.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +408 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +59 -27
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +13 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +26 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.40.0.txt +40 -0
  17. data/doc/release_notes/5.41.0.txt +25 -0
  18. data/doc/release_notes/5.42.0.txt +136 -0
  19. data/doc/release_notes/5.43.0.txt +98 -0
  20. data/doc/release_notes/5.44.0.txt +32 -0
  21. data/doc/release_notes/5.45.0.txt +34 -0
  22. data/doc/release_notes/5.46.0.txt +87 -0
  23. data/doc/release_notes/5.47.0.txt +59 -0
  24. data/doc/release_notes/5.48.0.txt +14 -0
  25. data/doc/release_notes/5.49.0.txt +59 -0
  26. data/doc/release_notes/5.50.0.txt +78 -0
  27. data/doc/release_notes/5.51.0.txt +47 -0
  28. data/doc/release_notes/5.52.0.txt +87 -0
  29. data/doc/release_notes/5.53.0.txt +23 -0
  30. data/doc/release_notes/5.54.0.txt +27 -0
  31. data/doc/release_notes/5.55.0.txt +21 -0
  32. data/doc/release_notes/5.56.0.txt +51 -0
  33. data/doc/release_notes/5.57.0.txt +23 -0
  34. data/doc/release_notes/5.58.0.txt +31 -0
  35. data/doc/release_notes/5.59.0.txt +73 -0
  36. data/doc/release_notes/5.60.0.txt +22 -0
  37. data/doc/release_notes/5.61.0.txt +43 -0
  38. data/doc/release_notes/5.62.0.txt +132 -0
  39. data/doc/release_notes/5.63.0.txt +33 -0
  40. data/doc/release_notes/5.64.0.txt +50 -0
  41. data/doc/release_notes/5.65.0.txt +21 -0
  42. data/doc/release_notes/5.66.0.txt +24 -0
  43. data/doc/release_notes/5.67.0.txt +32 -0
  44. data/doc/release_notes/5.68.0.txt +61 -0
  45. data/doc/release_notes/5.69.0.txt +26 -0
  46. data/doc/release_notes/5.70.0.txt +35 -0
  47. data/doc/release_notes/5.71.0.txt +21 -0
  48. data/doc/release_notes/5.72.0.txt +33 -0
  49. data/doc/schema_modification.rdoc +1 -1
  50. data/doc/security.rdoc +9 -9
  51. data/doc/sharding.rdoc +3 -1
  52. data/doc/sql.rdoc +28 -16
  53. data/doc/testing.rdoc +22 -11
  54. data/doc/transactions.rdoc +6 -6
  55. data/doc/virtual_rows.rdoc +2 -2
  56. data/lib/sequel/adapters/ado/access.rb +1 -1
  57. data/lib/sequel/adapters/ado.rb +17 -17
  58. data/lib/sequel/adapters/amalgalite.rb +3 -5
  59. data/lib/sequel/adapters/ibmdb.rb +2 -2
  60. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  61. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  62. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  64. data/lib/sequel/adapters/jdbc.rb +16 -18
  65. data/lib/sequel/adapters/mysql.rb +92 -67
  66. data/lib/sequel/adapters/mysql2.rb +54 -49
  67. data/lib/sequel/adapters/odbc.rb +6 -2
  68. data/lib/sequel/adapters/oracle.rb +4 -3
  69. data/lib/sequel/adapters/postgres.rb +83 -40
  70. data/lib/sequel/adapters/shared/access.rb +11 -1
  71. data/lib/sequel/adapters/shared/db2.rb +30 -0
  72. data/lib/sequel/adapters/shared/mssql.rb +90 -9
  73. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  74. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  75. data/lib/sequel/adapters/shared/postgres.rb +496 -178
  76. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  77. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  78. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  79. data/lib/sequel/adapters/sqlite.rb +60 -18
  80. data/lib/sequel/adapters/tinytds.rb +1 -1
  81. data/lib/sequel/adapters/trilogy.rb +117 -0
  82. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  83. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  84. data/lib/sequel/ast_transformer.rb +6 -0
  85. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  86. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  87. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  88. data/lib/sequel/connection_pool/single.rb +6 -8
  89. data/lib/sequel/connection_pool/threaded.rb +14 -8
  90. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  91. data/lib/sequel/connection_pool.rb +55 -31
  92. data/lib/sequel/core.rb +28 -18
  93. data/lib/sequel/database/connecting.rb +27 -3
  94. data/lib/sequel/database/dataset.rb +16 -6
  95. data/lib/sequel/database/misc.rb +69 -14
  96. data/lib/sequel/database/query.rb +73 -2
  97. data/lib/sequel/database/schema_generator.rb +46 -53
  98. data/lib/sequel/database/schema_methods.rb +18 -2
  99. data/lib/sequel/dataset/actions.rb +108 -14
  100. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  101. data/lib/sequel/dataset/features.rb +20 -0
  102. data/lib/sequel/dataset/misc.rb +12 -2
  103. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  104. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  105. data/lib/sequel/dataset/query.rb +171 -44
  106. data/lib/sequel/dataset/sql.rb +182 -47
  107. data/lib/sequel/dataset.rb +4 -0
  108. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  109. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  110. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  111. data/lib/sequel/extensions/async_thread_pool.rb +439 -0
  112. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  113. data/lib/sequel/extensions/blank.rb +8 -0
  114. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  115. data/lib/sequel/extensions/connection_validator.rb +16 -11
  116. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  117. data/lib/sequel/extensions/core_refinements.rb +36 -11
  118. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  119. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  120. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  121. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  122. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  123. data/lib/sequel/extensions/index_caching.rb +5 -1
  124. data/lib/sequel/extensions/inflector.rb +9 -1
  125. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  126. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  127. data/lib/sequel/extensions/migration.rb +11 -2
  128. data/lib/sequel/extensions/named_timezones.rb +26 -6
  129. data/lib/sequel/extensions/pagination.rb +1 -1
  130. data/lib/sequel/extensions/pg_array.rb +32 -4
  131. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  132. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  133. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  134. data/lib/sequel/extensions/pg_enum.rb +2 -3
  135. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  136. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  137. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  138. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  139. data/lib/sequel/extensions/pg_inet.rb +10 -11
  140. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  141. data/lib/sequel/extensions/pg_interval.rb +45 -19
  142. data/lib/sequel/extensions/pg_json.rb +13 -15
  143. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  144. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +11 -24
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +21 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/query.rb +2 -0
  151. data/lib/sequel/extensions/s.rb +2 -1
  152. data/lib/sequel/extensions/schema_caching.rb +1 -1
  153. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  154. data/lib/sequel/extensions/server_block.rb +10 -13
  155. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  156. data/lib/sequel/extensions/sql_comments.rb +110 -3
  157. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  158. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  159. data/lib/sequel/extensions/string_agg.rb +1 -1
  160. data/lib/sequel/extensions/string_date_time.rb +19 -23
  161. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  162. data/lib/sequel/model/associations.rb +345 -101
  163. data/lib/sequel/model/base.rb +51 -27
  164. data/lib/sequel/model/dataset_module.rb +3 -0
  165. data/lib/sequel/model/errors.rb +10 -1
  166. data/lib/sequel/model/inflections.rb +1 -1
  167. data/lib/sequel/model/plugins.rb +5 -0
  168. data/lib/sequel/plugins/association_proxies.rb +2 -0
  169. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +87 -15
  172. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  173. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  174. data/lib/sequel/plugins/column_encryption.rb +728 -0
  175. data/lib/sequel/plugins/composition.rb +10 -4
  176. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  177. data/lib/sequel/plugins/constraint_validations.rb +10 -6
  178. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  179. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  180. data/lib/sequel/plugins/dirty.rb +1 -1
  181. data/lib/sequel/plugins/enum.rb +124 -0
  182. data/lib/sequel/plugins/finder.rb +4 -2
  183. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  184. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  185. data/lib/sequel/plugins/json_serializer.rb +39 -24
  186. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  187. data/lib/sequel/plugins/list.rb +3 -1
  188. data/lib/sequel/plugins/many_through_many.rb +109 -10
  189. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  190. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  191. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  192. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  193. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  194. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
  195. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  196. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  197. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  198. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  199. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  200. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  201. data/lib/sequel/plugins/serialization.rb +9 -3
  202. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  203. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  204. data/lib/sequel/plugins/sql_comments.rb +189 -0
  205. data/lib/sequel/plugins/static_cache.rb +39 -1
  206. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  207. data/lib/sequel/plugins/subclasses.rb +28 -11
  208. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  209. data/lib/sequel/plugins/timestamps.rb +1 -1
  210. data/lib/sequel/plugins/unused_associations.rb +521 -0
  211. data/lib/sequel/plugins/update_or_create.rb +1 -1
  212. data/lib/sequel/plugins/validate_associated.rb +22 -12
  213. data/lib/sequel/plugins/validation_helpers.rb +46 -12
  214. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  215. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  216. data/lib/sequel/sql.rb +1 -1
  217. data/lib/sequel/timezones.rb +12 -14
  218. data/lib/sequel/version.rb +1 -1
  219. metadata +132 -38
@@ -22,13 +22,14 @@
22
22
  module Sequel
23
23
  module Postgres
24
24
  module ExtendedDateSupport
25
- DATE_YEAR_1 = Date.new(1)
26
25
  DATETIME_YEAR_1 = DateTime.new(1)
27
26
  TIME_YEAR_1 = Time.at(-62135596800).utc
28
27
  INFINITE_TIMESTAMP_STRINGS = ['infinity'.freeze, '-infinity'.freeze].freeze
29
28
  INFINITE_DATETIME_VALUES = ([PLUS_INFINITY, MINUS_INFINITY] + INFINITE_TIMESTAMP_STRINGS).freeze
30
29
  PLUS_DATE_INFINITY = Date::Infinity.new
31
30
  MINUS_DATE_INFINITY = -PLUS_DATE_INFINITY
31
+ RATIONAL_60 = Rational(60)
32
+ TIME_CAN_PARSE_BC = RUBY_VERSION >= '2.5'
32
33
 
33
34
  # Add dataset methods and update the conversion proces for dates and timestamps.
34
35
  def self.extended(db)
@@ -36,6 +37,27 @@ module Sequel
36
37
  procs = db.conversion_procs
37
38
  procs[1082] = ::Sequel.method(:string_to_date)
38
39
  procs[1184] = procs[1114] = db.method(:to_application_timestamp)
40
+ if ocps = db.instance_variable_get(:@oid_convertor_map)
41
+ # Clear the oid convertor map entries for timestamps if they
42
+ # exist, so it will regenerate new ones that use this extension.
43
+ # This is only taken when using the jdbc adapter.
44
+ Sequel.synchronize do
45
+ ocps.delete(1184)
46
+ ocps.delete(1114)
47
+ end
48
+ end
49
+ end
50
+
51
+ # Handle BC dates and times in bound variables. This is necessary for Date values
52
+ # when using both the postgres and jdbc adapters, but also necessary for Time values
53
+ # on jdbc.
54
+ def bound_variable_arg(arg, conn)
55
+ case arg
56
+ when Date, Time
57
+ literal(arg)
58
+ else
59
+ super
60
+ end
39
61
  end
40
62
 
41
63
  # Whether infinite timestamps/dates should be converted on retrieval. By default, no
@@ -83,30 +105,21 @@ module Sequel
83
105
  # If convert_infinite_timestamps is true and the value is infinite, return an appropriate
84
106
  # value based on the convert_infinite_timestamps setting.
85
107
  def to_application_timestamp(value)
86
- if value.is_a?(String) && (m = value.match(/((?:[-+]\d\d:\d\d)(:\d\d)?)?( BC)?\z/)) && (m[2] || m[3])
108
+ if value.is_a?(String) && (m = /((?:[-+]\d\d:\d\d)(:\d\d)?)?( BC)?\z/.match(value)) && (m[2] || m[3])
87
109
  if m[3]
88
110
  value = value.sub(' BC', '').sub(' ', ' BC ')
89
- conv = defined?(JRUBY_VERSION) && JRUBY_VERSION == '9.2.0.0'
90
111
  end
91
- if m[2] || conv
92
- dt = DateTime.parse(value)
93
- if conv
94
- # :nocov:
95
- if Sequel.datetime_class == DateTime
96
- dt >>= 12
97
- else
98
- dt >>= 24
99
- end
100
- # :nocov:
101
- end
102
- unless Sequel.datetime_class == DateTime
103
- dt = dt.to_time
104
- if conv && (timezone == nil || timezone == :local) && !m[1]
105
- # :nocov:
106
- dt = Sequel.send(:convert_input_timestamp, dt.strftime("%F %T.%6N"), :local)
107
- # :nocov:
108
- end
112
+ if m[2]
113
+ dt = if Sequel.datetime_class == DateTime
114
+ DateTime.parse(value)
115
+ elsif TIME_CAN_PARSE_BC
116
+ Time.parse(value)
117
+ # :nocov:
118
+ else
119
+ DateTime.parse(value).to_time
120
+ # :nocov:
109
121
  end
122
+
110
123
  Sequel.convert_output_timestamp(dt, Sequel.application_timezone)
111
124
  else
112
125
  super(value)
@@ -176,7 +189,7 @@ module Sequel
176
189
 
177
190
  # Handle BC Date objects.
178
191
  def literal_date(date)
179
- if date < DATE_YEAR_1
192
+ if date.year < 1
180
193
  date <<= ((date.year) * 24 - 12)
181
194
  date.strftime("'%Y-%m-%d BC'")
182
195
  else
@@ -223,10 +236,7 @@ module Sequel
223
236
  # Work around JRuby bug #4822 in Time#to_datetime for times before date of calendar reform
224
237
  def literal_time(time)
225
238
  if time < TIME_YEAR_1
226
- dt = DateTime.parse(super)
227
- # Work around JRuby bug #5191
228
- dt >>= 12 if JRUBY_VERSION == '9.2.0.0'
229
- literal_datetime(dt)
239
+ literal_datetime(DateTime.parse(super))
230
240
  else
231
241
  super
232
242
  end
@@ -236,7 +246,8 @@ module Sequel
236
246
  # Handle BC Time objects.
237
247
  def literal_time(time)
238
248
  if time < TIME_YEAR_1
239
- literal_datetime(time.to_datetime)
249
+ time = db.from_application_timestamp(time)
250
+ time.strftime("'#{sprintf('%04i', time.year.abs+1)}-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*(time.utc_offset/RATIONAL_60).divmod(60))} BC'")
240
251
  else
241
252
  super
242
253
  end
@@ -0,0 +1,116 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The pg_extended_integer_support extension supports literalizing
4
+ # Ruby integers outside of PostgreSQL bigint range on PostgreSQL.
5
+ # Sequel by default will raise exceptions when
6
+ # literalizing such integers, as PostgreSQL would treat them
7
+ # as numeric type values instead of integer/bigint type values
8
+ # if unquoted, which can result in unexpected negative performance
9
+ # (e.g. forcing sequential scans when index scans would be used for
10
+ # an integer/bigint type).
11
+ #
12
+ # To load the extension into a Dataset (this returns a new Dataset):
13
+ #
14
+ # dataset = dataset.extension(:pg_extended_integer_support)
15
+ #
16
+ # To load the extension into a Database, so it affects all of the
17
+ # Database's datasets:
18
+ #
19
+ # DB.extension :pg_extended_integer_support
20
+ #
21
+ # By default, the extension will quote integers outside
22
+ # bigint range:
23
+ #
24
+ # DB.literal(2**63) # => "'9223372036854775808'"
25
+ #
26
+ # Quoting the value treats the type as unknown:
27
+ #
28
+ # DB.get{pg_typeof(2**63)} # => 'unknown'
29
+ #
30
+ # PostgreSQL will implicitly cast the unknown type to the appropriate
31
+ # database type, raising an error if it cannot be casted. Be aware this
32
+ # can result in the integer value being implicitly casted to text or
33
+ # any other PostgreSQL type:
34
+ #
35
+ # # Returns a string, not an integer:
36
+ # DB.get{2**63}
37
+ # # => "9223372036854775808"
38
+ #
39
+ # You can use the Dataset#integer_outside_bigint_range_strategy method
40
+ # with the value +:raw+ to change the strategy to not quote the variable:
41
+ #
42
+ # DB.dataset.
43
+ # integer_outside_bigint_range_strategy(:raw).
44
+ # literal(2**63)
45
+ # # => "9223372036854775808"
46
+ #
47
+ # Note that not quoting the value will result in PostgreSQL treating
48
+ # the type as numeric instead of integer:
49
+ #
50
+ # DB.dataset.
51
+ # integer_outside_bigint_range_strategy(:raw).
52
+ # get{pg_typeof(2**63)}
53
+ # # => "numeric"
54
+ #
55
+ # The +:raw+ behavior was Sequel's historical behavior, but unless
56
+ # you fully understand the reprecussions of PostgreSQL using a
57
+ # numeric type for integer values, you should not use it.
58
+ #
59
+ # To get the current default behavior of raising an exception for
60
+ # integers outside of PostgreSQL bigint range, you can use a strategy
61
+ # of +:raise+.
62
+ #
63
+ # To specify a default strategy for handling integers outside
64
+ # bigint range that applies to all of a Database's datasets, you can
65
+ # use the +:integer_outside_bigint_range_strategy+ Database option with
66
+ # a value of +:raise+ or +:raw+:
67
+ #
68
+ # DB.opts[:integer_outside_bigint_range_strategy] = :raw
69
+ #
70
+ # The Database option will be used as a fallback if you did not call
71
+ # the Dataset#integer_outside_bigint_range_strategy method to specify
72
+ # a strategy for the dataset.
73
+ #
74
+ # Related module: Sequel::Postgres::ExtendedIntegerSupport
75
+
76
+ #
77
+ module Sequel
78
+ module Postgres
79
+ module ExtendedIntegerSupport
80
+ # Set the strategy for handling integers outside PostgreSQL
81
+ # bigint range. Supported values:
82
+ #
83
+ # :quote :: Quote the integer value. PostgreSQL will treat
84
+ # the integer as a unknown type, implicitly casting
85
+ # to any other type as needed. This is the default
86
+ # value when using the pg_extended_integer_support
87
+ # extension.
88
+ # :raise :: Raise error when attempting to literalize the integer
89
+ # (the default behavior of Sequel on PostgreSQL when
90
+ # not using the pg_extended_integer_support extension).
91
+ # :raw :: Use raw integer value without quoting. PostgreSQL
92
+ # will treat the integer as a numeric. This was Sequel's
93
+ # historical behavior, but it is unlikely to be desired.
94
+ def integer_outside_bigint_range_strategy(strategy)
95
+ clone(:integer_outside_bigint_range_strategy=>strategy)
96
+ end
97
+
98
+ private
99
+
100
+ # Handle integers outside the bigint range by using
101
+ # the configured strategy.
102
+ def literal_integer_outside_bigint_range(v)
103
+ case @opts[:integer_outside_bigint_range_strategy] || @db.opts[:integer_outside_bigint_range_strategy]
104
+ when :raise
105
+ super
106
+ when :raw
107
+ v.to_s
108
+ else # when :quote
109
+ "'#{v}'"
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ Dataset.register_extension(:pg_extended_integer_support, Postgres::ExtendedIntegerSupport)
116
+ end
@@ -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