sequel 5.39.0 → 5.72.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 (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
@@ -8,9 +8,10 @@
8
8
  # DB.extension :date_arithmetic
9
9
  #
10
10
  # Then you can use the Sequel.date_add and Sequel.date_sub methods
11
- # to return Sequel expressions:
11
+ # to return Sequel expressions (this example shows the only supported
12
+ # keys for the second argument):
12
13
  #
13
- # add = Sequel.date_add(:date_column, years: 1, months: 2, days: 3)
14
+ # add = Sequel.date_add(:date_column, years: 1, months: 2, weeks: 2, days: 1)
14
15
  # sub = Sequel.date_sub(:date_column, hours: 1, minutes: 2, seconds: 3)
15
16
  #
16
17
  # In addition to specifying the interval as a hash, there is also
@@ -24,13 +25,17 @@
24
25
  # By default, values are casted to the generic timestamp type for the
25
26
  # database. You can override the cast type using the :cast option:
26
27
  #
27
- # add = Sequel.date_add(:date_column, {years: 1, months: 2, days: 3}, :cast=>:timestamptz)
28
+ # add = Sequel.date_add(:date_column, {years: 1, months: 2, days: 3}, cast: :timestamptz)
28
29
  #
29
30
  # These expressions can be used in your datasets, or anywhere else that
30
31
  # Sequel expressions are allowed:
31
32
  #
32
33
  # DB[:table].select(add.as(:d)).where(sub > Sequel::CURRENT_TIMESTAMP)
33
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
+ #
34
39
  # Related module: Sequel::SQL::DateAdd
35
40
 
36
41
  #
@@ -49,14 +54,21 @@ module Sequel
49
54
  # Options:
50
55
  # :cast :: Cast to the specified type instead of the default if casting
51
56
  def date_sub(expr, interval, opts=OPTS)
52
- interval = if interval.is_a?(Hash)
53
- h = {}
54
- interval.each{|k,v| h[k] = -v unless v.nil?}
55
- h
56
- else
57
- -interval
57
+ if defined?(ActiveSupport::Duration) && interval.is_a?(ActiveSupport::Duration)
58
+ interval = interval.parts
58
59
  end
59
- DateAdd.new(expr, interval, opts)
60
+ parts = {}
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
71
+ DateAdd.new(expr, parts, opts)
60
72
  end
61
73
  end
62
74
 
@@ -69,6 +81,7 @@ module Sequel
69
81
  module DatasetMethods
70
82
  DURATION_UNITS = [:years, :months, :days, :hours, :minutes, :seconds].freeze
71
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
72
85
  MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
73
86
  MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
74
87
  H2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s[0...-1].freeze}).freeze
@@ -88,14 +101,28 @@ module Sequel
88
101
 
89
102
  cast = case db_type = db.database_type
90
103
  when :postgres
91
- interval = String.new
92
- each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
93
- 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?
94
120
  end
95
- if interval.empty?
96
- return literal_append(sql, Sequel.cast(expr, cast_type))
121
+
122
+ if interval
123
+ return complex_expression_sql_append(sql, :+, [casted, interval])
97
124
  else
98
- return complex_expression_sql_append(sql, :+, [Sequel.cast(expr, cast_type), Sequel.cast(interval, :interval)])
125
+ return literal_append(sql, casted)
99
126
  end
100
127
  when :sqlite
101
128
  args = [expr]
@@ -113,12 +140,12 @@ module Sequel
113
140
  end
114
141
  when :mssql, :h2, :access, :sqlanywhere
115
142
  units = case db_type
116
- when :mssql, :sqlanywhere
117
- MSSQL_DURATION_UNITS
118
143
  when :h2
119
144
  H2_DURATION_UNITS
120
145
  when :access
121
146
  ACCESS_DURATION_UNITS
147
+ else
148
+ MSSQL_DURATION_UNITS
122
149
  end
123
150
  each_valid_interval_unit(h, units) do |value, sql_unit|
124
151
  expr = Sequel.function(:DATEADD, sql_unit, value, expr)
@@ -186,22 +213,35 @@ module Sequel
186
213
  # ActiveSupport::Duration :: Converted to a hash using the interval's parts.
187
214
  def initialize(expr, interval, opts=OPTS)
188
215
  @expr = expr
189
- @interval = if interval.is_a?(Hash)
190
- interval.each_value do |v|
191
- # Attempt to prevent SQL injection by users who pass untrusted strings
192
- # as interval values.
193
- if v.is_a?(String) && !v.is_a?(LiteralString)
194
- raise Sequel::InvalidValue, "cannot provide String value as interval part: #{v.inspect}"
195
- end
216
+
217
+ h = Hash.new(0)
218
+ interval = interval.parts unless interval.is_a?(Hash)
219
+ interval.each do |unit, value|
220
+ # skip nil values
221
+ next unless value
222
+
223
+ # Convert weeks to days, as ActiveSupport::Duration can use weeks,
224
+ # but the database-specific literalizers only support days.
225
+ if unit == :weeks
226
+ unit = :days
227
+ value *= 7
228
+ end
229
+
230
+ unless DatasetMethods::DURATION_UNITS.include?(unit)
231
+ raise Sequel::Error, "Invalid key used in DateAdd interval hash: #{unit.inspect}"
232
+ end
233
+
234
+ # Attempt to prevent SQL injection by users who pass untrusted strings
235
+ # as interval values. It doesn't make sense to support literal strings,
236
+ # due to the numeric adding below.
237
+ if value.is_a?(String)
238
+ raise Sequel::InvalidValue, "cannot provide String value as interval part: #{value.inspect}"
196
239
  end
197
- Hash[interval]
198
- else
199
- h = Hash.new(0)
200
- interval.parts.each{|unit, value| h[unit] += value}
201
- Hash[h]
240
+
241
+ h[unit] += value
202
242
  end
203
243
 
204
- @interval.freeze
244
+ @interval = Hash[h].freeze
205
245
  @cast_type = opts[:cast] if opts[:cast]
206
246
  freeze
207
247
  end
@@ -0,0 +1,67 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The date_parse_input_handler extension allows for configuring how input
4
+ # to date parsing methods should be handled. By default, the
5
+ # extension does not change behavior. However, you can use the
6
+ # +Sequel.date_parse_input_handler+ method to support custom handling
7
+ # of input strings to the date parsing methods. For example, if you want
8
+ # to implement a length check to prevent denial of service vulnerabilities
9
+ # in older versions of Ruby, you can do:
10
+ #
11
+ # Sequel.extension :date_parse_input_handler
12
+ # Sequel.date_parse_input_handler do |string|
13
+ # raise Sequel::InvalidValue, "string length (200) exceeds the limit 128" if string.bytesize > 128
14
+ # string
15
+ # end
16
+ #
17
+ # You can also use +Sequel.date_parse_input_handler+ to modify the string
18
+ # that will be passed to the parsing methods. For example, you could
19
+ # truncate it:
20
+ #
21
+ # Sequel.date_parse_input_handler do |string|
22
+ # string.b[0, 128]
23
+ # end
24
+ #
25
+ # Be aware that modern versions of Ruby will raise an exception if
26
+ # date parsing input exceeds 128 bytes.
27
+
28
+ module Sequel
29
+ module DateParseInputHandler
30
+ def date_parse_input_handler(&block)
31
+ singleton_class.class_eval do
32
+ define_method(:handle_date_parse_input, &block)
33
+ private :handle_date_parse_input
34
+ alias handle_date_parse_input handle_date_parse_input
35
+ end
36
+ end
37
+
38
+ # Call date parse input handler with input string.
39
+ def string_to_date(string)
40
+ super(handle_date_parse_input(string))
41
+ end
42
+
43
+ # Call date parse input handler with input string.
44
+ def string_to_datetime(string)
45
+ super(handle_date_parse_input(string))
46
+ end
47
+
48
+ # Call date parse input handler with input string.
49
+ def string_to_time(string)
50
+ super(handle_date_parse_input(string))
51
+ end
52
+
53
+ private
54
+
55
+ # Call date parse input handler with input string.
56
+ def _date_parse(string)
57
+ super(handle_date_parse_input(string))
58
+ end
59
+
60
+ # Return string as-is by default, so by default behavior does not change.
61
+ def handle_date_parse_input(string)
62
+ string
63
+ end
64
+ end
65
+
66
+ extend DateParseInputHandler
67
+ end
@@ -19,7 +19,11 @@ module Sequel::DateTimeParseToTime
19
19
  # Use DateTime.parse.to_time to do the conversion if the input a string and is assumed to
20
20
  # be in UTC and there is no offset information in the string.
21
21
  def convert_input_timestamp(v, input_timezone)
22
- if v.is_a?(String) && datetime_class == Time && input_timezone == :utc && !Date._parse(v).has_key?(:offset)
22
+ if v.is_a?(String) && datetime_class == Time && input_timezone == :utc && !_date_parse(v).has_key?(:offset)
23
+ # :nocov:
24
+ # Whether this is fully branch covered depends on the order in which the specs are run.
25
+ v = handle_date_parse_input(v) if respond_to?(:handle_date_parse_input, true)
26
+ # :nocov:
23
27
  t = DateTime.parse(v).to_time
24
28
  case application_timezone
25
29
  when nil, :local
@@ -44,7 +44,7 @@ module Sequel
44
44
  # :nocov:
45
45
 
46
46
  # Customize handling of duplicate columns for this dataset.
47
- def on_duplicate_columns(handler = (raise Error, "Must provide either an argument or a block to on_duplicate_columns" unless block_given?; nil), &block)
47
+ def on_duplicate_columns(handler = (raise Error, "Must provide either an argument or a block to on_duplicate_columns" unless defined?(yield); nil), &block)
48
48
  raise Error, "Cannot provide both an argument and a block to on_duplicate_columns" if handler && block
49
49
  clone(:on_duplicate_columns=>handler||block)
50
50
  end
@@ -55,6 +55,8 @@ module Sequel
55
55
 
56
56
  module SQL
57
57
  class Expression
58
+ alias inspect inspect
59
+
58
60
  # Attempt to produce a string suitable for eval, such that:
59
61
  #
60
62
  # eval(obj.inspect) == obj
@@ -56,7 +56,11 @@ module Sequel
56
56
 
57
57
  # Dump the index cache to the filename given in Marshal format.
58
58
  def dump_index_cache(file)
59
- File.open(file, 'wb'){|f| f.write(Marshal.dump(@indexes))}
59
+ indexes = {}
60
+ @indexes.sort.each do |k, v|
61
+ indexes[k] = v
62
+ end
63
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(indexes))}
60
64
  nil
61
65
  end
62
66
 
@@ -102,9 +102,17 @@ class String
102
102
  # Yield the Inflections module if a block is given, and return
103
103
  # the Inflections module.
104
104
  def self.inflections
105
- yield Inflections if block_given?
105
+ yield Inflections if defined?(yield)
106
106
  Inflections
107
107
  end
108
+
109
+ %w'classify constantize dasherize demodulize foreign_key humanize pluralize singularize tableize underscore'.each do |m|
110
+ # :nocov:
111
+ if method_defined?(m)
112
+ alias_method(m, m)
113
+ end
114
+ # :nocov:
115
+ end
108
116
 
109
117
  # By default, camelize converts the string to UpperCamelCase. If the argument to camelize
110
118
  # is set to :lower then camelize produces lowerCamelCase.
@@ -0,0 +1,141 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The is_distinct_from extension adds the ability to use the
4
+ # SQL standard IS DISTINCT FROM operator, which is similar to the
5
+ # not equals operator, except that NULL values are considered
6
+ # equal. PostgreSQL, SQLite 3.39+, and H2 currently support this operator. On
7
+ # other databases, support is emulated.
8
+ #
9
+ # First, you need to load the extension into the database:
10
+ #
11
+ # DB.extension :is_distinct_from
12
+ #
13
+ # Then you can use the Sequel.is_distinct_from to create the expression
14
+ # objects:
15
+ #
16
+ # expr = Sequel.is_distinct_from(:column_a, :column_b)
17
+ # # (column_a IS DISTINCT FROM column_b)
18
+ #
19
+ # You can also use the +is_distinct_from+ method on most Sequel expressions:
20
+ #
21
+ # expr = Sequel[:column_a].is_distinct_from(:column_b)
22
+ # # (column_a IS DISTINCT FROM column_b)
23
+ #
24
+ # These expressions can be used in your datasets, or anywhere else that
25
+ # Sequel expressions are allowed:
26
+ #
27
+ # DB[:table].where(expr)
28
+ #
29
+ # Related module: Sequel::SQL::IsDistinctFrom
30
+
31
+ #
32
+ module Sequel
33
+ module SQL
34
+ module Builders
35
+ # Return a IsDistinctFrom expression object, using the IS DISTINCT FROM operator
36
+ # with the given left hand side and right hand side.
37
+ def is_distinct_from(lhs, rhs)
38
+ BooleanExpression.new(:NOOP, IsDistinctFrom.new(lhs, rhs))
39
+ end
40
+ end
41
+
42
+ # Represents an SQL expression using the IS DISTINCT FROM operator.
43
+ class IsDistinctFrom < GenericExpression
44
+ # These methods are added to expressions, allowing them to return IS DISTINCT
45
+ # FROM expressions based on the receiving expression.
46
+ module Methods
47
+ # Return a IsDistinctFrom expression, using the IS DISTINCT FROM operator,
48
+ # with the receiver as the left hand side and the argument as the right hand side.
49
+ def is_distinct_from(rhs)
50
+ BooleanExpression.new(:NOOP, IsDistinctFrom.new(self, rhs))
51
+ end
52
+ end
53
+
54
+ # These methods are added to datasets using the is_distinct_from extension
55
+ # extension, for the purposes of correctly literalizing IsDistinctFrom
56
+ # expressions for the appropriate database type.
57
+ module DatasetMethods
58
+ # Append the SQL fragment for the IS DISTINCT FROM expression to the SQL query.
59
+ def is_distinct_from_sql_append(sql, idf)
60
+ lhs = idf.lhs
61
+ rhs = idf.rhs
62
+
63
+ if supports_is_distinct_from?
64
+ sql << "("
65
+ literal_append(sql, lhs)
66
+ sql << " IS DISTINCT FROM "
67
+ literal_append(sql, rhs)
68
+ sql << ")"
69
+ elsif db.database_type == :derby && (lhs == nil || rhs == nil)
70
+ if lhs == nil && rhs == nil
71
+ sql << literal_false
72
+ elsif lhs == nil
73
+ literal_append(sql, ~Sequel.expr(rhs=>nil))
74
+ else
75
+ literal_append(sql, ~Sequel.expr(lhs=>nil))
76
+ end
77
+ else
78
+ literal_append(sql, Sequel.case({(Sequel.expr(lhs=>rhs) | [[lhs, nil], [rhs, nil]]) => 0}, 1) => 1)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # Whether the database supports IS DISTINCT FROM.
85
+ def supports_is_distinct_from?
86
+ if defined?(super)
87
+ return super
88
+ end
89
+
90
+ case db.database_type
91
+ when :postgres, :h2
92
+ true
93
+ when :sqlite
94
+ db.sqlite_version >= 33900
95
+ else
96
+ false
97
+ end
98
+ end
99
+ end
100
+
101
+ # The left hand side of the IS DISTINCT FROM expression.
102
+ attr_reader :lhs
103
+
104
+ # The right hand side of the IS DISTINCT FROM expression.
105
+ attr_reader :rhs
106
+
107
+ def initialize(lhs, rhs)
108
+ @lhs = lhs
109
+ @rhs = rhs
110
+ end
111
+
112
+ to_s_method :is_distinct_from_sql
113
+ end
114
+ end
115
+
116
+ class SQL::GenericExpression
117
+ include SQL::IsDistinctFrom::Methods
118
+ end
119
+
120
+ class LiteralString
121
+ include SQL::IsDistinctFrom::Methods
122
+ end
123
+
124
+ Dataset.register_extension(:is_distinct_from, SQL::IsDistinctFrom::DatasetMethods)
125
+ end
126
+
127
+ # :nocov:
128
+ if Sequel.core_extensions?
129
+ class Symbol
130
+ include Sequel::SQL::IsDistinctFrom::Methods
131
+ end
132
+ end
133
+
134
+ if defined?(Sequel::CoreRefinements)
135
+ module Sequel::CoreRefinements
136
+ refine Symbol do
137
+ send INCLUDE_METH, Sequel::SQL::IsDistinctFrom::Methods
138
+ end
139
+ end
140
+ end
141
+ # :nocov:
@@ -8,6 +8,9 @@
8
8
  # :decimal :: use 0.0 for unsupported strings
9
9
  # :string :: silently allow hash and array conversion to string
10
10
  #
11
+ # This also removes bytesize checks for string inputs for float, integer
12
+ # and decimal conversions.
13
+ #
11
14
  # To load the extension into the database:
12
15
  #
13
16
  # DB.extension :looser_typecasting
@@ -68,7 +68,9 @@ module Sequel
68
68
  # Allow calling private methods for backwards compatibility
69
69
  @db.send(method_sym, *args, &block)
70
70
  end
71
+ # :nocov:
71
72
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
73
+ # :nocov:
72
74
 
73
75
  # This object responds to all methods the database responds to.
74
76
  def respond_to_missing?(meth, include_private)
@@ -268,6 +270,10 @@ module Sequel
268
270
  def rename_column(name, new_name)
269
271
  @actions << [:rename_column, new_name, name]
270
272
  end
273
+
274
+ def set_column_allow_null(name, allow_null=true)
275
+ @actions << [:set_column_allow_null, name, !allow_null]
276
+ end
271
277
  end
272
278
 
273
279
  # The preferred method for writing Sequel migrations, using a DSL:
@@ -375,7 +381,7 @@ module Sequel
375
381
  # Raise a NotCurrentError unless the migrator is current, takes the same
376
382
  # arguments as #run.
377
383
  def self.check_current(*args)
378
- raise(NotCurrentError, 'migrator is not current') unless is_current?(*args)
384
+ raise(NotCurrentError, 'current migration version does not match latest available version') unless is_current?(*args)
379
385
  end
380
386
 
381
387
  # Return whether the migrator is current (i.e. it does not need to make
@@ -386,6 +392,9 @@ module Sequel
386
392
 
387
393
  # Migrates the supplied database using the migration files in the specified directory. Options:
388
394
  # :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
395
+ # It is very risky to use this option, since it can result in
396
+ # the database schema version number not matching the expected
397
+ # database schema.
389
398
  # :column :: The column in the :table argument storing the migration version (default: :version).
390
399
  # :current :: The current version of the database. If not given, it is retrieved from the database
391
400
  # using the :table and :column options.
@@ -540,7 +549,7 @@ module Sequel
540
549
 
541
550
  @direction = current < target ? :up : :down
542
551
 
543
- if @direction == :down && @current >= @files.length
552
+ if @direction == :down && @current >= @files.length && !@allow_missing_migration_files
544
553
  raise Migrator::Error, "Missing migration version(s) needed to migrate down to target version (current: #{current}, target: #{target})"
545
554
  end
546
555
 
@@ -68,6 +68,10 @@ module Sequel
68
68
  private
69
69
 
70
70
  if RUBY_VERSION >= '2.6'
71
+ # Whether Time.at with :nsec and :in is broken. True on JRuby < 9.3.9.0.
72
+ BROKEN_TIME_AT_WITH_NSEC = defined?(JRUBY_VERSION) && (JRUBY_VERSION < '9.3' || (JRUBY_VERSION < '9.4' && JRUBY_VERSION.split('.')[2].to_i < 9))
73
+ private_constant :BROKEN_TIME_AT_WITH_NSEC
74
+
71
75
  # Convert the given input Time (which must be in UTC) to the given input timezone,
72
76
  # which should be a TZInfo::Timezone instance.
73
77
  def convert_input_time_other(v, input_timezone)
@@ -76,35 +80,49 @@ module Sequel
76
80
  raise unless disamb = tzinfo_disambiguator_for(v)
77
81
  period = input_timezone.period_for_local(v, &disamb)
78
82
  offset = period.utc_total_offset
79
- Time.at(v.to_i - offset, :in => input_timezone)
83
+ # :nocov:
84
+ if BROKEN_TIME_AT_WITH_NSEC
85
+ Time.at(v.to_i - offset, :in => input_timezone) + v.nsec/1000000000.0
86
+ # :nocov:
87
+ else
88
+ Time.at(v.to_i - offset, v.nsec, :nsec, :in => input_timezone)
89
+ end
80
90
  end
81
91
 
82
92
  # Convert the given input Time to the given output timezone,
83
93
  # which should be a TZInfo::Timezone instance.
84
94
  def convert_output_time_other(v, output_timezone)
85
- Time.at(v.to_i, :in => output_timezone)
95
+ # :nocov:
96
+ if BROKEN_TIME_AT_WITH_NSEC
97
+ Time.at(v.to_i, :in => output_timezone) + v.nsec/1000000000.0
98
+ # :nocov:
99
+ else
100
+ Time.at(v.to_i, v.nsec, :nsec, :in => output_timezone)
101
+ end
86
102
  end
87
- else
88
103
  # :nodoc:
89
104
  # :nocov:
105
+ else
90
106
  def convert_input_time_other(v, input_timezone)
91
107
  local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
92
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
108
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
93
109
  end
94
110
 
95
111
  if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
96
112
  def convert_output_time_other(v, output_timezone)
97
113
  v = output_timezone.utc_to_local(v.getutc)
98
114
  local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
99
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + local_offset
115
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0 + local_offset
100
116
  end
101
117
  else
102
118
  def convert_output_time_other(v, output_timezone)
103
119
  v = output_timezone.utc_to_local(v.getutc)
104
120
  local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
105
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
121
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
106
122
  end
107
123
  end
124
+ # :nodoc:
125
+ # :nocov:
108
126
  end
109
127
 
110
128
  # Handle both TZInfo 1 and TZInfo 2
@@ -142,6 +160,8 @@ module Sequel
142
160
  # Convert timezone offset from UTC to the offset for the output_timezone
143
161
  (v - local_offset).new_offset(local_offset)
144
162
  end
163
+ # :nodoc:
164
+ # :nocov:
145
165
  end
146
166
 
147
167
  # Returns TZInfo::Timezone instance if given a String.
@@ -54,7 +54,7 @@ module Sequel
54
54
  # an enumerator if no block is given.
55
55
  def each_page(page_size)
56
56
  raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
57
- return to_enum(:each_page, page_size) unless block_given?
57
+ return to_enum(:each_page, page_size) unless defined?(yield)
58
58
  record_count = count
59
59
  total_pages = (record_count / page_size.to_f).ceil
60
60
  (1..total_pages).each{|page_no| yield paginate(page_no, page_size, record_count)}
@@ -213,6 +213,7 @@ module Sequel
213
213
  scalar_typecast_method = :"typecast_value_#{opts.fetch(:scalar_typecast, type)}"
214
214
  define_method(meth){|v| typecast_value_pg_array(v, creator, scalar_typecast_method)}
215
215
  private meth
216
+ alias_method(meth, meth)
216
217
  end
217
218
 
218
219
  @schema_type_classes[:"#{type}_array"] = PGArray
@@ -227,16 +228,35 @@ module Sequel
227
228
  when Array
228
229
  "{#{a.map{|i| bound_variable_array(i)}.join(',')}}"
229
230
  when Sequel::SQL::Blob
230
- "\"#{literal(a)[BLOB_RANGE].gsub("''", "'").gsub(/("|\\)/, '\\\\\1')}\""
231
+ bound_variable_array_string(literal(a)[BLOB_RANGE].gsub("''", "'"))
231
232
  when Sequel::LiteralString
232
233
  a
233
234
  when String
234
- "\"#{a.gsub(/("|\\)/, '\\\\\1')}\""
235
+ bound_variable_array_string(a)
236
+ when Float
237
+ if a.infinite?
238
+ a > 0 ? '"Infinity"' : '"-Infinity"'
239
+ elsif a.nan?
240
+ '"NaN"'
241
+ else
242
+ literal(a)
243
+ end
235
244
  else
236
- literal(a)
245
+ if (s = bound_variable_arg(a, nil)).is_a?(String)
246
+ bound_variable_array_string(s)
247
+ else
248
+ literal(a)
249
+ end
237
250
  end
238
251
  end
239
252
 
253
+ # Escape strings used as array members in bound variables. Most complex
254
+ # will create a regular string with bound_variable_arg, and then use this
255
+ # escaping to format it as an array member.
256
+ def bound_variable_array_string(s)
257
+ "\"#{s.gsub(/("|\\)/, '\\\\\1')}\""
258
+ end
259
+
240
260
  # Look into both the current database's array schema types and the global
241
261
  # array schema types to get the type symbol for the given database type
242
262
  # string.
@@ -245,7 +265,7 @@ module Sequel
245
265
  end
246
266
 
247
267
  # Make the column type detection handle registered array types.
248
- def schema_column_type(db_type)
268
+ def schema_array_type(db_type)
249
269
  if (db_type =~ /\A([^(]+)(?:\([^(]+\))?\[\]\z/io) && (type = pg_array_schema_type($1))
250
270
  type
251
271
  else
@@ -456,6 +476,14 @@ module Sequel
456
476
  end
457
477
  end
458
478
 
479
+ # Allow automatic parameterization of the receiver if all elements can be
480
+ # can be automatically parameterized.
481
+ def sequel_auto_param_type(ds)
482
+ if array_type && all?{|x| nil == x || ds.send(:auto_param_type, x)}
483
+ "::#{array_type}[]"
484
+ end
485
+ end
486
+
459
487
  private
460
488
 
461
489
  # Recursive method that handles multi-dimensional
@@ -108,7 +108,7 @@ module Sequel
108
108
 
109
109
  # Call the ANY function:
110
110
  #
111
- # array_op.all # ANY(array)
111
+ # array_op.any # ANY(array)
112
112
  #
113
113
  # Usually used like:
114
114
  #
@@ -329,7 +329,7 @@ end
329
329
  if defined?(Sequel::CoreRefinements)
330
330
  module Sequel::CoreRefinements
331
331
  refine Symbol do
332
- include Sequel::Postgres::ArrayOpMethods
332
+ send INCLUDE_METH, Sequel::Postgres::ArrayOpMethods
333
333
  end
334
334
  end
335
335
  end