sequel 5.45.0 → 5.77.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +434 -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 +27 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +28 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.46.0.txt +87 -0
  17. data/doc/release_notes/5.47.0.txt +59 -0
  18. data/doc/release_notes/5.48.0.txt +14 -0
  19. data/doc/release_notes/5.49.0.txt +59 -0
  20. data/doc/release_notes/5.50.0.txt +78 -0
  21. data/doc/release_notes/5.51.0.txt +47 -0
  22. data/doc/release_notes/5.52.0.txt +87 -0
  23. data/doc/release_notes/5.53.0.txt +23 -0
  24. data/doc/release_notes/5.54.0.txt +27 -0
  25. data/doc/release_notes/5.55.0.txt +21 -0
  26. data/doc/release_notes/5.56.0.txt +51 -0
  27. data/doc/release_notes/5.57.0.txt +23 -0
  28. data/doc/release_notes/5.58.0.txt +31 -0
  29. data/doc/release_notes/5.59.0.txt +73 -0
  30. data/doc/release_notes/5.60.0.txt +22 -0
  31. data/doc/release_notes/5.61.0.txt +43 -0
  32. data/doc/release_notes/5.62.0.txt +132 -0
  33. data/doc/release_notes/5.63.0.txt +33 -0
  34. data/doc/release_notes/5.64.0.txt +50 -0
  35. data/doc/release_notes/5.65.0.txt +21 -0
  36. data/doc/release_notes/5.66.0.txt +24 -0
  37. data/doc/release_notes/5.67.0.txt +32 -0
  38. data/doc/release_notes/5.68.0.txt +61 -0
  39. data/doc/release_notes/5.69.0.txt +26 -0
  40. data/doc/release_notes/5.70.0.txt +35 -0
  41. data/doc/release_notes/5.71.0.txt +21 -0
  42. data/doc/release_notes/5.72.0.txt +33 -0
  43. data/doc/release_notes/5.73.0.txt +66 -0
  44. data/doc/release_notes/5.74.0.txt +45 -0
  45. data/doc/release_notes/5.75.0.txt +35 -0
  46. data/doc/release_notes/5.76.0.txt +86 -0
  47. data/doc/release_notes/5.77.0.txt +63 -0
  48. data/doc/schema_modification.rdoc +1 -1
  49. data/doc/security.rdoc +9 -9
  50. data/doc/sharding.rdoc +3 -1
  51. data/doc/sql.rdoc +27 -15
  52. data/doc/testing.rdoc +23 -13
  53. data/doc/transactions.rdoc +6 -6
  54. data/doc/virtual_rows.rdoc +1 -1
  55. data/lib/sequel/adapters/ado/access.rb +1 -1
  56. data/lib/sequel/adapters/ado.rb +1 -1
  57. data/lib/sequel/adapters/amalgalite.rb +3 -5
  58. data/lib/sequel/adapters/ibmdb.rb +3 -3
  59. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  60. data/lib/sequel/adapters/jdbc/h2.rb +63 -10
  61. data/lib/sequel/adapters/jdbc/hsqldb.rb +8 -0
  62. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  63. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  64. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  65. data/lib/sequel/adapters/jdbc.rb +24 -22
  66. data/lib/sequel/adapters/mysql.rb +92 -67
  67. data/lib/sequel/adapters/mysql2.rb +56 -51
  68. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  69. data/lib/sequel/adapters/odbc.rb +1 -1
  70. data/lib/sequel/adapters/oracle.rb +4 -3
  71. data/lib/sequel/adapters/postgres.rb +89 -45
  72. data/lib/sequel/adapters/shared/access.rb +11 -1
  73. data/lib/sequel/adapters/shared/db2.rb +42 -0
  74. data/lib/sequel/adapters/shared/mssql.rb +91 -10
  75. data/lib/sequel/adapters/shared/mysql.rb +78 -3
  76. data/lib/sequel/adapters/shared/oracle.rb +86 -7
  77. data/lib/sequel/adapters/shared/postgres.rb +576 -171
  78. data/lib/sequel/adapters/shared/sqlanywhere.rb +21 -5
  79. data/lib/sequel/adapters/shared/sqlite.rb +92 -8
  80. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  81. data/lib/sequel/adapters/sqlite.rb +99 -18
  82. data/lib/sequel/adapters/tinytds.rb +1 -1
  83. data/lib/sequel/adapters/trilogy.rb +117 -0
  84. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  85. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  86. data/lib/sequel/ast_transformer.rb +6 -0
  87. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  88. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  89. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  90. data/lib/sequel/connection_pool/single.rb +6 -8
  91. data/lib/sequel/connection_pool/threaded.rb +14 -8
  92. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  93. data/lib/sequel/connection_pool.rb +57 -31
  94. data/lib/sequel/core.rb +17 -18
  95. data/lib/sequel/database/connecting.rb +27 -3
  96. data/lib/sequel/database/dataset.rb +16 -6
  97. data/lib/sequel/database/misc.rb +70 -14
  98. data/lib/sequel/database/query.rb +73 -2
  99. data/lib/sequel/database/schema_generator.rb +11 -6
  100. data/lib/sequel/database/schema_methods.rb +23 -4
  101. data/lib/sequel/database/transactions.rb +6 -0
  102. data/lib/sequel/dataset/actions.rb +111 -15
  103. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  104. data/lib/sequel/dataset/features.rb +20 -1
  105. data/lib/sequel/dataset/misc.rb +12 -2
  106. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  107. data/lib/sequel/dataset/query.rb +170 -41
  108. data/lib/sequel/dataset/sql.rb +190 -71
  109. data/lib/sequel/dataset.rb +4 -0
  110. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  111. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  112. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  113. data/lib/sequel/extensions/async_thread_pool.rb +14 -13
  114. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  115. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  116. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  117. data/lib/sequel/extensions/connection_validator.rb +16 -11
  118. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  119. data/lib/sequel/extensions/core_refinements.rb +36 -11
  120. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  121. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  122. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  123. data/lib/sequel/extensions/duplicate_columns_handler.rb +11 -10
  124. data/lib/sequel/extensions/index_caching.rb +5 -1
  125. data/lib/sequel/extensions/inflector.rb +1 -1
  126. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  127. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  128. data/lib/sequel/extensions/migration.rb +57 -15
  129. data/lib/sequel/extensions/named_timezones.rb +22 -6
  130. data/lib/sequel/extensions/pagination.rb +1 -1
  131. data/lib/sequel/extensions/pg_array.rb +33 -4
  132. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  133. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  134. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  135. data/lib/sequel/extensions/pg_enum.rb +1 -2
  136. data/lib/sequel/extensions/pg_extended_date_support.rb +39 -28
  137. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  138. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  139. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  140. data/lib/sequel/extensions/pg_inet.rb +10 -11
  141. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  142. data/lib/sequel/extensions/pg_interval.rb +11 -11
  143. data/lib/sequel/extensions/pg_json.rb +13 -15
  144. data/lib/sequel/extensions/pg_json_ops.rb +125 -2
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +13 -26
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +20 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  151. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  152. data/lib/sequel/extensions/s.rb +2 -1
  153. data/lib/sequel/extensions/schema_caching.rb +1 -1
  154. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  155. data/lib/sequel/extensions/server_block.rb +10 -13
  156. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  157. data/lib/sequel/extensions/sql_comments.rb +110 -3
  158. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  159. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  160. data/lib/sequel/extensions/string_agg.rb +1 -1
  161. data/lib/sequel/extensions/string_date_time.rb +19 -23
  162. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  163. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  164. data/lib/sequel/model/associations.rb +286 -92
  165. data/lib/sequel/model/base.rb +53 -33
  166. data/lib/sequel/model/dataset_module.rb +3 -0
  167. data/lib/sequel/model/errors.rb +10 -1
  168. data/lib/sequel/model/exceptions.rb +15 -3
  169. data/lib/sequel/model/inflections.rb +1 -1
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +74 -16
  172. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  173. data/lib/sequel/plugins/column_encryption.rb +29 -8
  174. data/lib/sequel/plugins/composition.rb +3 -2
  175. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  176. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  177. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  178. data/lib/sequel/plugins/dirty.rb +1 -1
  179. data/lib/sequel/plugins/enum.rb +124 -0
  180. data/lib/sequel/plugins/finder.rb +4 -2
  181. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  182. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  183. data/lib/sequel/plugins/json_serializer.rb +2 -2
  184. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  185. data/lib/sequel/plugins/list.rb +8 -3
  186. data/lib/sequel/plugins/many_through_many.rb +109 -10
  187. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  188. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  189. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  190. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  191. data/lib/sequel/plugins/paged_operations.rb +181 -0
  192. data/lib/sequel/plugins/pg_array_associations.rb +46 -34
  193. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  194. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  195. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  196. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  197. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  198. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  199. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  200. data/lib/sequel/plugins/serialization.rb +1 -0
  201. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  202. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  203. data/lib/sequel/plugins/sql_comments.rb +189 -0
  204. data/lib/sequel/plugins/static_cache.rb +39 -1
  205. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  206. data/lib/sequel/plugins/subclasses.rb +28 -11
  207. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  208. data/lib/sequel/plugins/timestamps.rb +1 -1
  209. data/lib/sequel/plugins/unused_associations.rb +521 -0
  210. data/lib/sequel/plugins/update_or_create.rb +1 -1
  211. data/lib/sequel/plugins/validate_associated.rb +22 -12
  212. data/lib/sequel/plugins/validation_helpers.rb +41 -11
  213. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  214. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  215. data/lib/sequel/sql.rb +1 -1
  216. data/lib/sequel/timezones.rb +12 -14
  217. data/lib/sequel/version.rb +1 -1
  218. metadata +109 -19
@@ -28,22 +28,22 @@
28
28
  # connections on every checkout without setting up coarse
29
29
  # connection checkouts will hurt performance, in some cases
30
30
  # significantly. Note that setting up coarse connection
31
- # checkouts reduces the concurrency level acheivable. For
31
+ # checkouts reduces the concurrency level achievable. For
32
32
  # example, in a web application, using Database#synchronize
33
33
  # in a rack middleware will limit the number of concurrent
34
34
  # web requests to the number to connections in the database
35
35
  # connection pool.
36
36
  #
37
- # Note that this extension only affects the default threaded
38
- # and the sharded threaded connection pool. The single
39
- # threaded and sharded single threaded connection pools are
40
- # not affected. As the only reason to use the single threaded
37
+ # Note that this extension does not work with the single
38
+ # threaded and sharded single threaded connection pools.
39
+ # As the only reason to use the single threaded
41
40
  # pools is for speed, and this extension makes the connection
42
41
  # pool slower, there's not much point in modifying this
43
42
  # extension to work with the single threaded pools. The
44
- # threaded pools work fine even in single threaded code, so if
45
- # you are currently using a single threaded pool and want to
46
- # use this extension, switch to using a threaded pool.
43
+ # non-single threaded pools work fine even in single threaded
44
+ # code, so if you are currently using a single threaded pool
45
+ # and want to use this extension, switch to using another
46
+ # pool.
47
47
  #
48
48
  # Related module: Sequel::ConnectionValidator
49
49
 
@@ -61,6 +61,11 @@ module Sequel
61
61
 
62
62
  # Initialize the data structures used by this extension.
63
63
  def self.extended(pool)
64
+ case pool.pool_type
65
+ when :single, :sharded_single
66
+ raise Error, "cannot load connection_validator extension if using single or sharded_single connection pool"
67
+ end
68
+
64
69
  pool.instance_exec do
65
70
  sync do
66
71
  @connection_timestamps ||= {}
@@ -103,8 +108,9 @@ module Sequel
103
108
  Sequel.elapsed_seconds_since(timer) > @connection_validation_timeout &&
104
109
  !db.valid_connection?(conn)
105
110
 
106
- if pool_type == :sharded_threaded
107
- sync{allocated(a.last).delete(Sequel.current)}
111
+ case pool_type
112
+ when :sharded_threaded, :sharded_timed_queue
113
+ sync{@allocated[a.last].delete(Sequel.current)}
108
114
  else
109
115
  sync{@allocated.delete(Sequel.current)}
110
116
  end
@@ -120,4 +126,3 @@ module Sequel
120
126
 
121
127
  Database.register_extension(:connection_validator){|db| db.pool.extend(ConnectionValidator)}
122
128
  end
123
-
@@ -126,7 +126,7 @@
126
126
  # be emulated by dropping the table and recreating it with the constraints.
127
127
  # If you want to use this plugin on SQLite with an alter_table block,
128
128
  # you should drop all constraint validation metadata using
129
- # <tt>drop_constraint_validations_for(:table=>'table')</tt>, and then
129
+ # <tt>drop_constraint_validations_for(table: 'table')</tt>, and then
130
130
  # readd all constraints you want to use inside the alter table block,
131
131
  # making no other changes inside the alter_table block.
132
132
  #
@@ -15,6 +15,12 @@ raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VE
15
15
  # :nocov:
16
16
 
17
17
  module Sequel::CoreRefinements
18
+ # :nocov:
19
+ include_meth = RUBY_VERSION >= '3.1' ? :import_methods : :include
20
+ # :nocov:
21
+ INCLUDE_METH = include_meth
22
+ private_constant :INCLUDE_METH
23
+
18
24
  refine Array do
19
25
  # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, not matching all of the
20
26
  # conditions.
@@ -161,8 +167,8 @@ module Sequel::CoreRefinements
161
167
  end
162
168
 
163
169
  refine String do
164
- include Sequel::SQL::AliasMethods
165
- include Sequel::SQL::CastMethods
170
+ send include_meth, Sequel::SQL::AliasMethods
171
+ send include_meth, Sequel::SQL::CastMethods
166
172
 
167
173
  # Converts a string into a <tt>Sequel::LiteralString</tt>, in order to override string
168
174
  # literalization, e.g.:
@@ -189,15 +195,34 @@ module Sequel::CoreRefinements
189
195
  end
190
196
 
191
197
  refine Symbol do
192
- include Sequel::SQL::AliasMethods
193
- include Sequel::SQL::CastMethods
194
- include Sequel::SQL::OrderMethods
195
- include Sequel::SQL::BooleanMethods
196
- include Sequel::SQL::NumericMethods
197
- include Sequel::SQL::QualifyingMethods
198
- include Sequel::SQL::StringMethods
199
- include Sequel::SQL::SubscriptMethods
200
- include Sequel::SQL::ComplexExpressionMethods
198
+ send include_meth, Sequel::SQL::AliasMethods
199
+ send include_meth, Sequel::SQL::CastMethods
200
+ send include_meth, Sequel::SQL::OrderMethods
201
+ send include_meth, Sequel::SQL::BooleanMethods
202
+ send include_meth, Sequel::SQL::NumericMethods
203
+
204
+ # :nocov:
205
+ remove_method :* if RUBY_VERSION >= '3.1'
206
+ # :nocov:
207
+
208
+ send include_meth, Sequel::SQL::QualifyingMethods
209
+ send include_meth, Sequel::SQL::StringMethods
210
+ send include_meth, Sequel::SQL::SubscriptMethods
211
+ send include_meth, Sequel::SQL::ComplexExpressionMethods
212
+
213
+ # :nocov:
214
+ if RUBY_VERSION >= '3.1'
215
+ remove_method :*
216
+ def *(ce=(arg=false;nil))
217
+ if arg == false
218
+ Sequel::SQL::ColumnAll.new(self)
219
+ else
220
+ Sequel::SQL::NumericExpression.new(:*, self, ce)
221
+ end
222
+ end
223
+
224
+ end
225
+ # :nocov:
201
226
 
202
227
  # Returns receiver wrapped in an <tt>Sequel::SQL::Identifier</tt>.
203
228
  #
@@ -25,13 +25,17 @@
25
25
  # By default, values are casted to the generic timestamp type for the
26
26
  # database. You can override the cast type using the :cast option:
27
27
  #
28
- # 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)
29
29
  #
30
30
  # These expressions can be used in your datasets, or anywhere else that
31
31
  # Sequel expressions are allowed:
32
32
  #
33
33
  # DB[:table].select(add.as(:d)).where(sub > Sequel::CURRENT_TIMESTAMP)
34
34
  #
35
+ # On most databases, the values you provide for years/months/days/etc. must
36
+ # be numeric values and not arbitrary SQL expressions. However, on PostgreSQL
37
+ # 9.4+, use of arbitrary SQL expressions is supported.
38
+ #
35
39
  # Related module: Sequel::SQL::DateAdd
36
40
 
37
41
  #
@@ -54,7 +58,16 @@ module Sequel
54
58
  interval = interval.parts
55
59
  end
56
60
  parts = {}
57
- interval.each{|k,v| parts[k] = -v unless v.nil?}
61
+ interval.each do |k,v|
62
+ case v
63
+ when nil
64
+ # ignore
65
+ when Numeric
66
+ parts[k] = -v
67
+ else
68
+ parts[k] = Sequel::SQL::NumericExpression.new(:*, v, -1)
69
+ end
70
+ end
58
71
  DateAdd.new(expr, parts, opts)
59
72
  end
60
73
  end
@@ -68,6 +81,7 @@ module Sequel
68
81
  module DatasetMethods
69
82
  DURATION_UNITS = [:years, :months, :days, :hours, :minutes, :seconds].freeze
70
83
  DEF_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s.freeze}).freeze
84
+ POSTGRES_DURATION_UNITS = DURATION_UNITS.zip([:years, :months, :days, :hours, :mins, :secs].map{|s| s.to_s.freeze}).freeze
71
85
  MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
72
86
  MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
73
87
  H2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s[0...-1].freeze}).freeze
@@ -87,14 +101,28 @@ module Sequel
87
101
 
88
102
  cast = case db_type = db.database_type
89
103
  when :postgres
90
- interval = String.new
91
- each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
92
- interval << "#{value} #{sql_unit} "
104
+ casted = Sequel.cast(expr, cast_type)
105
+
106
+ if db.server_version >= 90400
107
+ placeholder = []
108
+ vals = []
109
+ each_valid_interval_unit(h, POSTGRES_DURATION_UNITS) do |value, sql_unit|
110
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
111
+ vals << value
112
+ end
113
+ interval = Sequel.function(:make_interval, Sequel.lit(placeholder, *vals)) unless vals.empty?
114
+ else
115
+ parts = String.new
116
+ each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
117
+ parts << "#{value} #{sql_unit} "
118
+ end
119
+ interval = Sequel.cast(parts, :interval) unless parts.empty?
93
120
  end
94
- if interval.empty?
95
- return literal_append(sql, Sequel.cast(expr, cast_type))
121
+
122
+ if interval
123
+ return complex_expression_sql_append(sql, :+, [casted, interval])
96
124
  else
97
- return complex_expression_sql_append(sql, :+, [Sequel.cast(expr, cast_type), Sequel.cast(interval, :interval)])
125
+ return literal_append(sql, casted)
98
126
  end
99
127
  when :sqlite
100
128
  args = [expr]
@@ -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
@@ -14,12 +14,12 @@
14
14
  #
15
15
  # ds = DB[:items].extension(:duplicate_columns_handler)
16
16
  #
17
- # A database option is introduced: :on_duplicate_columns. It accepts a Symbol
18
- # or any object that responds to :call.
17
+ # If the Database option :on_duplicate_columns is set, it configures how this
18
+ # extension works. The value should be # or any object that responds to :call.
19
19
  #
20
- # on_duplicate_columns: :raise
21
- # on_duplicate_columns: :warn
22
- # on_duplicate_columns: :ignore
20
+ # on_duplicate_columns: :raise # or 'raise'
21
+ # on_duplicate_columns: :warn # or 'warn'
22
+ # on_duplicate_columns: :ignore # or anything unrecognized
23
23
  # on_duplicate_columns: lambda{|columns| arbitrary_condition? ? :raise : :warn}
24
24
  #
25
25
  # You may also configure duplicate columns handling for a specific dataset:
@@ -30,9 +30,10 @@
30
30
  # ds.on_duplicate_columns{|columns| arbitrary_condition? ? :raise : :warn}
31
31
  # ds.on_duplicate_columns(lambda{|columns| arbitrary_condition? ? :raise : :warn})
32
32
  #
33
- # If :raise is specified, a Sequel::DuplicateColumnError is raised.
34
- # If :warn is specified, you will receive a warning via +warn+.
33
+ # If :raise or 'raise' is specified, a Sequel::DuplicateColumnError is raised.
34
+ # If :warn or 'warn' is specified, you will receive a warning via +warn+.
35
35
  # If a callable is specified, it will be called.
36
+ # For other values, duplicate columns are ignored (Sequel's default behavior)
36
37
  # If no on_duplicate_columns is specified, the default is :warn.
37
38
  #
38
39
  # Related module: Sequel::DuplicateColumnsHandler
@@ -44,7 +45,7 @@ module Sequel
44
45
  # :nocov:
45
46
 
46
47
  # 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)
48
+ 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
49
  raise Error, "Cannot provide both an argument and a block to on_duplicate_columns" if handler && block
49
50
  clone(:on_duplicate_columns=>handler||block)
50
51
  end
@@ -64,9 +65,9 @@ module Sequel
64
65
  message = "#{caller(*CALLER_ARGS).first}: One or more duplicate columns present in #{cols.inspect}"
65
66
 
66
67
  case duplicate_columns_handler_type(cols)
67
- when :raise
68
+ when :raise, 'raise'
68
69
  raise DuplicateColumnError, message
69
- when :warn
70
+ when :warn, 'warn'
70
71
  warn message
71
72
  end
72
73
  end
@@ -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,7 +102,7 @@ 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
108
 
@@ -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
@@ -159,6 +159,19 @@ module Sequel
159
159
  migration.up = block
160
160
  migration.down = MigrationReverser.new.reverse(&block)
161
161
  end
162
+
163
+ # Creates a revert migration. This is the same as creating
164
+ # the same block with +down+, but it also calls the block and attempts
165
+ # to create a +up+ block that will reverse the changes made by
166
+ # the block. This is designed to revert the changes in the
167
+ # provided block.
168
+ #
169
+ # There are no guarantees that this will work perfectly
170
+ # in all cases, but it works for some simple cases.
171
+ def revert(&block)
172
+ migration.down = block
173
+ migration.up = MigrationReverser.new.reverse(&block)
174
+ end
162
175
  end
163
176
 
164
177
  # Handles the reversing of reversible migrations. Basically records
@@ -270,6 +283,10 @@ module Sequel
270
283
  def rename_column(name, new_name)
271
284
  @actions << [:rename_column, new_name, name]
272
285
  end
286
+
287
+ def set_column_allow_null(name, allow_null=true)
288
+ @actions << [:set_column_allow_null, name, !allow_null]
289
+ end
273
290
  end
274
291
 
275
292
  # The preferred method for writing Sequel migrations, using a DSL:
@@ -377,7 +394,7 @@ module Sequel
377
394
  # Raise a NotCurrentError unless the migrator is current, takes the same
378
395
  # arguments as #run.
379
396
  def self.check_current(*args)
380
- raise(NotCurrentError, 'migrator is not current') unless is_current?(*args)
397
+ raise(NotCurrentError, 'current migration version does not match latest available version') unless is_current?(*args)
381
398
  end
382
399
 
383
400
  # Return whether the migrator is current (i.e. it does not need to make
@@ -388,6 +405,9 @@ module Sequel
388
405
 
389
406
  # Migrates the supplied database using the migration files in the specified directory. Options:
390
407
  # :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
408
+ # It is very risky to use this option, since it can result in
409
+ # the database schema version number not matching the expected
410
+ # database schema.
391
411
  # :column :: The column in the :table argument storing the migration version (default: :version).
392
412
  # :current :: The current version of the database. If not given, it is retrieved from the database
393
413
  # using the :table and :column options.
@@ -475,11 +495,7 @@ module Sequel
475
495
  @use_transactions
476
496
  end
477
497
 
478
- if use_trans
479
- db.transaction(&block)
480
- else
481
- yield
482
- end
498
+ db.transaction(:skip_transaction=>use_trans == false, &block)
483
499
  end
484
500
 
485
501
  # Load the migration file, raising an exception if the file does not define
@@ -542,7 +558,7 @@ module Sequel
542
558
 
543
559
  @direction = current < target ? :up : :down
544
560
 
545
- if @direction == :down && @current >= @files.length
561
+ if @direction == :down && @current >= @files.length && !@allow_missing_migration_files
546
562
  raise Migrator::Error, "Missing migration version(s) needed to migrate down to target version (current: #{current}, target: #{target})"
547
563
  end
548
564
 
@@ -677,6 +693,13 @@ module Sequel
677
693
  @migration_tuples = get_migration_tuples
678
694
  end
679
695
 
696
+ # Apply the migration in the given file path. See Migrator.run for the
697
+ # available options. Additionally, this method supports the :direction
698
+ # option for whether to run the migration up (default) or down.
699
+ def self.run_single(db, path, opts=OPTS)
700
+ new(db, File.dirname(path), opts).run_single(path, opts[:direction] || :up)
701
+ end
702
+
680
703
  # The timestamp migrator is current if there are no migrations to apply
681
704
  # in either direction.
682
705
  def is_current?
@@ -686,20 +709,39 @@ module Sequel
686
709
  # Apply all migration tuples on the database
687
710
  def run
688
711
  migration_tuples.each do |m, f, direction|
689
- t = Time.now
690
- db.log_info("Begin applying migration #{f}, direction: #{direction}")
691
- checked_transaction(m) do
692
- m.apply(db, direction)
693
- fi = f.downcase
694
- direction == :up ? ds.insert(column=>fi) : ds.where(column=>fi).delete
695
- end
696
- db.log_info("Finished applying migration #{f}, direction: #{direction}, took #{sprintf('%0.6f', Time.now - t)} seconds")
712
+ apply_migration(m, f, direction)
697
713
  end
698
714
  nil
699
715
  end
700
716
 
717
+ # Apply single migration tuple at the given path with the given direction
718
+ # on the database.
719
+ def run_single(path, direction)
720
+ migration = load_migration_file(path)
721
+ file_name = File.basename(path)
722
+ already_applied = applied_migrations.include?(file_name.downcase)
723
+
724
+ return if direction == :up ? already_applied : !already_applied
725
+
726
+ apply_migration(migration, file_name, direction)
727
+ nil
728
+ end
729
+
701
730
  private
702
731
 
732
+ # Apply a single migration with the given filename in the given direction.
733
+ def apply_migration(migration, file_name, direction)
734
+ fi = file_name.downcase
735
+ t = Time.now
736
+
737
+ db.log_info("Begin applying migration #{file_name}, direction: #{direction}")
738
+ checked_transaction(migration) do
739
+ migration.apply(db, direction)
740
+ direction == :up ? ds.insert(column=>fi) : ds.where(column=>fi).delete
741
+ end
742
+ db.log_info("Finished applying migration #{file_name}, direction: #{direction}, took #{sprintf('%0.6f', Time.now - t)} seconds")
743
+ end
744
+
703
745
  # Convert the schema_info table to the new schema_migrations table format,
704
746
  # using the version of the schema_info table and the current migration files.
705
747
  def convert_from_schema_info