sequel 5.33.0 → 5.58.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +318 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +40 -9
  5. data/doc/association_basics.rdoc +77 -13
  6. data/doc/cheat_sheet.rdoc +13 -5
  7. data/doc/code_order.rdoc +0 -12
  8. data/doc/dataset_filtering.rdoc +2 -2
  9. data/doc/fork_safety.rdoc +84 -0
  10. data/doc/migration.rdoc +12 -6
  11. data/doc/model_plugins.rdoc +1 -1
  12. data/doc/opening_databases.rdoc +15 -3
  13. data/doc/postgresql.rdoc +9 -1
  14. data/doc/querying.rdoc +7 -5
  15. data/doc/release_notes/5.34.0.txt +40 -0
  16. data/doc/release_notes/5.35.0.txt +56 -0
  17. data/doc/release_notes/5.36.0.txt +60 -0
  18. data/doc/release_notes/5.37.0.txt +30 -0
  19. data/doc/release_notes/5.38.0.txt +28 -0
  20. data/doc/release_notes/5.39.0.txt +19 -0
  21. data/doc/release_notes/5.40.0.txt +40 -0
  22. data/doc/release_notes/5.41.0.txt +25 -0
  23. data/doc/release_notes/5.42.0.txt +136 -0
  24. data/doc/release_notes/5.43.0.txt +98 -0
  25. data/doc/release_notes/5.44.0.txt +32 -0
  26. data/doc/release_notes/5.45.0.txt +34 -0
  27. data/doc/release_notes/5.46.0.txt +87 -0
  28. data/doc/release_notes/5.47.0.txt +59 -0
  29. data/doc/release_notes/5.48.0.txt +14 -0
  30. data/doc/release_notes/5.49.0.txt +59 -0
  31. data/doc/release_notes/5.50.0.txt +78 -0
  32. data/doc/release_notes/5.51.0.txt +47 -0
  33. data/doc/release_notes/5.52.0.txt +87 -0
  34. data/doc/release_notes/5.53.0.txt +23 -0
  35. data/doc/release_notes/5.54.0.txt +27 -0
  36. data/doc/release_notes/5.55.0.txt +21 -0
  37. data/doc/release_notes/5.56.0.txt +51 -0
  38. data/doc/release_notes/5.57.0.txt +23 -0
  39. data/doc/release_notes/5.58.0.txt +31 -0
  40. data/doc/sql.rdoc +14 -2
  41. data/doc/testing.rdoc +10 -1
  42. data/doc/transactions.rdoc +0 -8
  43. data/doc/validations.rdoc +1 -1
  44. data/doc/virtual_rows.rdoc +1 -1
  45. data/lib/sequel/adapters/ado/access.rb +1 -1
  46. data/lib/sequel/adapters/ado.rb +17 -17
  47. data/lib/sequel/adapters/amalgalite.rb +3 -5
  48. data/lib/sequel/adapters/ibmdb.rb +2 -2
  49. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  50. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  51. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  52. data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  54. data/lib/sequel/adapters/jdbc.rb +29 -19
  55. data/lib/sequel/adapters/mysql.rb +80 -67
  56. data/lib/sequel/adapters/mysql2.rb +54 -49
  57. data/lib/sequel/adapters/odbc.rb +8 -6
  58. data/lib/sequel/adapters/oracle.rb +5 -4
  59. data/lib/sequel/adapters/postgres.rb +27 -29
  60. data/lib/sequel/adapters/shared/access.rb +2 -0
  61. data/lib/sequel/adapters/shared/db2.rb +30 -0
  62. data/lib/sequel/adapters/shared/mssql.rb +84 -7
  63. data/lib/sequel/adapters/shared/mysql.rb +33 -2
  64. data/lib/sequel/adapters/shared/oracle.rb +82 -7
  65. data/lib/sequel/adapters/shared/postgres.rb +158 -20
  66. data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
  67. data/lib/sequel/adapters/shared/sqlite.rb +102 -10
  68. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  69. data/lib/sequel/adapters/sqlite.rb +60 -18
  70. data/lib/sequel/adapters/tinytds.rb +2 -1
  71. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  72. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -1
  73. data/lib/sequel/ast_transformer.rb +6 -0
  74. data/lib/sequel/connection_pool/sharded_single.rb +9 -8
  75. data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
  76. data/lib/sequel/connection_pool/single.rb +7 -9
  77. data/lib/sequel/connection_pool/threaded.rb +1 -1
  78. data/lib/sequel/core.rb +33 -24
  79. data/lib/sequel/database/connecting.rb +3 -4
  80. data/lib/sequel/database/misc.rb +37 -12
  81. data/lib/sequel/database/query.rb +3 -1
  82. data/lib/sequel/database/schema_generator.rb +50 -53
  83. data/lib/sequel/database/schema_methods.rb +45 -23
  84. data/lib/sequel/database/transactions.rb +9 -6
  85. data/lib/sequel/dataset/actions.rb +61 -8
  86. data/lib/sequel/dataset/features.rb +15 -0
  87. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +114 -11
  90. data/lib/sequel/dataset/sql.rb +172 -46
  91. data/lib/sequel/deprecated.rb +3 -1
  92. data/lib/sequel/exceptions.rb +2 -0
  93. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  94. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  95. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  96. data/lib/sequel/extensions/blank.rb +8 -0
  97. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  98. data/lib/sequel/extensions/core_refinements.rb +38 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +36 -24
  100. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  101. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  102. data/lib/sequel/extensions/duplicate_columns_handler.rb +3 -1
  103. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  104. data/lib/sequel/extensions/inflector.rb +9 -1
  105. data/lib/sequel/extensions/is_distinct_from.rb +139 -0
  106. data/lib/sequel/extensions/migration.rb +13 -2
  107. data/lib/sequel/extensions/named_timezones.rb +5 -1
  108. data/lib/sequel/extensions/pagination.rb +1 -1
  109. data/lib/sequel/extensions/pg_array.rb +1 -0
  110. data/lib/sequel/extensions/pg_array_ops.rb +6 -2
  111. data/lib/sequel/extensions/pg_enum.rb +3 -1
  112. data/lib/sequel/extensions/pg_extended_date_support.rb +2 -2
  113. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  114. data/lib/sequel/extensions/pg_hstore_ops.rb +55 -3
  115. data/lib/sequel/extensions/pg_inet.rb +2 -0
  116. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  117. data/lib/sequel/extensions/pg_interval.rb +35 -8
  118. data/lib/sequel/extensions/pg_json.rb +3 -5
  119. data/lib/sequel/extensions/pg_json_ops.rb +119 -4
  120. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  121. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  122. data/lib/sequel/extensions/pg_range.rb +7 -19
  123. data/lib/sequel/extensions/pg_range_ops.rb +39 -9
  124. data/lib/sequel/extensions/pg_row.rb +1 -1
  125. data/lib/sequel/extensions/pg_row_ops.rb +25 -1
  126. data/lib/sequel/extensions/query.rb +3 -0
  127. data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
  128. data/lib/sequel/extensions/s.rb +4 -1
  129. data/lib/sequel/extensions/schema_dumper.rb +16 -5
  130. data/lib/sequel/extensions/server_block.rb +8 -12
  131. data/lib/sequel/extensions/sql_comments.rb +110 -3
  132. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  133. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  134. data/lib/sequel/extensions/string_agg.rb +1 -1
  135. data/lib/sequel/extensions/string_date_time.rb +19 -23
  136. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  137. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  138. data/lib/sequel/extensions/to_dot.rb +9 -3
  139. data/lib/sequel/model/associations.rb +342 -114
  140. data/lib/sequel/model/base.rb +45 -24
  141. data/lib/sequel/model/errors.rb +10 -1
  142. data/lib/sequel/model/inflections.rb +1 -1
  143. data/lib/sequel/model/plugins.rb +8 -3
  144. data/lib/sequel/model.rb +3 -1
  145. data/lib/sequel/plugins/association_pks.rb +60 -18
  146. data/lib/sequel/plugins/association_proxies.rb +3 -0
  147. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  148. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  149. data/lib/sequel/plugins/auto_validations.rb +39 -5
  150. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  151. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  152. data/lib/sequel/plugins/class_table_inheritance.rb +3 -8
  153. data/lib/sequel/plugins/column_encryption.rb +728 -0
  154. data/lib/sequel/plugins/composition.rb +8 -2
  155. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  156. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  157. data/lib/sequel/plugins/csv_serializer.rb +2 -0
  158. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  159. data/lib/sequel/plugins/dirty.rb +44 -0
  160. data/lib/sequel/plugins/enum.rb +124 -0
  161. data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
  162. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  163. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  164. data/lib/sequel/plugins/json_serializer.rb +39 -24
  165. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  166. data/lib/sequel/plugins/many_through_many.rb +108 -9
  167. data/lib/sequel/plugins/nested_attributes.rb +8 -3
  168. data/lib/sequel/plugins/pg_array_associations.rb +58 -41
  169. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
  170. data/lib/sequel/plugins/prepared_statements.rb +15 -12
  171. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  172. data/lib/sequel/plugins/rcte_tree.rb +37 -35
  173. data/lib/sequel/plugins/serialization.rb +9 -3
  174. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  175. data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
  176. data/lib/sequel/plugins/sql_comments.rb +189 -0
  177. data/lib/sequel/plugins/static_cache.rb +1 -1
  178. data/lib/sequel/plugins/string_stripper.rb +1 -1
  179. data/lib/sequel/plugins/subclasses.rb +28 -11
  180. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
  181. data/lib/sequel/plugins/timestamps.rb +1 -1
  182. data/lib/sequel/plugins/tree.rb +9 -4
  183. data/lib/sequel/plugins/unused_associations.rb +521 -0
  184. data/lib/sequel/plugins/update_or_create.rb +1 -1
  185. data/lib/sequel/plugins/validation_class_methods.rb +5 -1
  186. data/lib/sequel/plugins/validation_helpers.rb +18 -11
  187. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  188. data/lib/sequel/sql.rb +1 -1
  189. data/lib/sequel/timezones.rb +20 -17
  190. data/lib/sequel/version.rb +1 -1
  191. metadata +93 -39
@@ -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
@@ -39,10 +39,12 @@
39
39
 
40
40
  module Sequel
41
41
  module DuplicateColumnsHandler
42
+ # :nocov:
42
43
  CALLER_ARGS = (RUBY_VERSION >= '2.0' ? [0,1] : [0]).freeze
44
+ # :nocov:
43
45
 
44
46
  # Customize handling of duplicate columns for this dataset.
45
- 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)
46
48
  raise Error, "Cannot provide both an argument and a block to on_duplicate_columns" if handler && block
47
49
  clone(:on_duplicate_columns=>handler||block)
48
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
@@ -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,139 @@
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. Only PostgreSQL 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
+ else
94
+ false
95
+ end
96
+ end
97
+ end
98
+
99
+ # The left hand side of the IS DISTINCT FROM expression.
100
+ attr_reader :lhs
101
+
102
+ # The right hand side of the IS DISTINCT FROM expression.
103
+ attr_reader :rhs
104
+
105
+ def initialize(lhs, rhs)
106
+ @lhs = lhs
107
+ @rhs = rhs
108
+ end
109
+
110
+ to_s_method :is_distinct_from_sql
111
+ end
112
+ end
113
+
114
+ class SQL::GenericExpression
115
+ include SQL::IsDistinctFrom::Methods
116
+ end
117
+
118
+ class LiteralString
119
+ include SQL::IsDistinctFrom::Methods
120
+ end
121
+
122
+ Dataset.register_extension(:is_distinct_from, SQL::IsDistinctFrom::DatasetMethods)
123
+ end
124
+
125
+ # :nocov:
126
+ if Sequel.core_extensions?
127
+ class Symbol
128
+ include Sequel::SQL::IsDistinctFrom::Methods
129
+ end
130
+ end
131
+
132
+ if defined?(Sequel::CoreRefinements)
133
+ module Sequel::CoreRefinements
134
+ refine Symbol do
135
+ send INCLUDE_METH, Sequel::SQL::IsDistinctFrom::Methods
136
+ end
137
+ end
138
+ end
139
+ # :nocov:
@@ -68,6 +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:
72
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
73
+ # :nocov:
71
74
 
72
75
  # This object responds to all methods the database responds to.
73
76
  def respond_to_missing?(meth, include_private)
@@ -329,7 +332,8 @@ module Sequel
329
332
  # schema_migrations for timestamped migrations). in the database to keep track
330
333
  # of the current migration version. If no migration version is stored in the
331
334
  # database, the version is considered to be 0. If no target version is
332
- # specified, the database is migrated to the latest version available in the
335
+ # specified, or the target version specified is greater than the latest
336
+ # version available, the database is migrated to the latest version available in the
333
337
  # migration directory.
334
338
  #
335
339
  # For example, to migrate the database to the latest version:
@@ -384,6 +388,9 @@ module Sequel
384
388
 
385
389
  # Migrates the supplied database using the migration files in the specified directory. Options:
386
390
  # :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
391
+ # It is very risky to use this option, since it can result in
392
+ # the database schema version number not matching the expected
393
+ # database schema.
387
394
  # :column :: The column in the :table argument storing the migration version (default: :version).
388
395
  # :current :: The current version of the database. If not given, it is retrieved from the database
389
396
  # using the :table and :column options.
@@ -518,7 +525,6 @@ module Sequel
518
525
  def initialize(db, directory, opts=OPTS)
519
526
  super
520
527
  @current = opts[:current] || current_migration_version
521
- raise(Error, "No current version available") unless current
522
528
 
523
529
  latest_version = latest_migration_version
524
530
  @target = if opts[:target]
@@ -538,6 +544,11 @@ module Sequel
538
544
  end
539
545
 
540
546
  @direction = current < target ? :up : :down
547
+
548
+ if @direction == :down && @current >= @files.length && !@allow_missing_migration_files
549
+ raise Migrator::Error, "Missing migration version(s) needed to migrate down to target version (current: #{current}, target: #{target})"
550
+ end
551
+
541
552
  @migrations = get_migrations
542
553
  end
543
554
 
@@ -84,9 +84,9 @@ module Sequel
84
84
  def convert_output_time_other(v, output_timezone)
85
85
  Time.at(v.to_i, :in => output_timezone)
86
86
  end
87
- else
88
87
  # :nodoc:
89
88
  # :nocov:
89
+ else
90
90
  def convert_input_time_other(v, input_timezone)
91
91
  local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
92
92
  Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
@@ -105,6 +105,8 @@ module Sequel
105
105
  Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
106
106
  end
107
107
  end
108
+ # :nodoc:
109
+ # :nocov:
108
110
  end
109
111
 
110
112
  # Handle both TZInfo 1 and TZInfo 2
@@ -142,6 +144,8 @@ module Sequel
142
144
  # Convert timezone offset from UTC to the offset for the output_timezone
143
145
  (v - local_offset).new_offset(local_offset)
144
146
  end
147
+ # :nodoc:
148
+ # :nocov:
145
149
  end
146
150
 
147
151
  # 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
@@ -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
  #
@@ -157,7 +157,9 @@ module Sequel
157
157
  else
158
158
  Sequel.function(:hstore, self, wrap_array(arg))
159
159
  end
160
+ # :nocov:
160
161
  if Sequel.respond_to?(:hstore_op)
162
+ # :nocov:
161
163
  v = Sequel.hstore_op(v)
162
164
  end
163
165
  v
@@ -283,7 +285,9 @@ module Sequel
283
285
  end
284
286
  end
285
287
 
288
+ # :nocov:
286
289
  if defined?(PGArray)
290
+ # :nocov:
287
291
  class PGArray
288
292
  # Wrap the PGArray instance in an ArrayOp, allowing you to easily use
289
293
  # the PostgreSQL array functions and operators with literal arrays.
@@ -325,7 +329,7 @@ end
325
329
  if defined?(Sequel::CoreRefinements)
326
330
  module Sequel::CoreRefinements
327
331
  refine Symbol do
328
- include Sequel::Postgres::ArrayOpMethods
332
+ send INCLUDE_METH, Sequel::Postgres::ArrayOpMethods
329
333
  end
330
334
  end
331
335
  end
@@ -42,7 +42,7 @@
42
42
  #
43
43
  # This extension integrates with the pg_array extension. If you plan
44
44
  # to use arrays of enum types, load the pg_array extension before the
45
- # pg_interval extension:
45
+ # pg_enum extension:
46
46
  #
47
47
  # DB.extension :pg_array, :pg_enum
48
48
  #
@@ -181,7 +181,9 @@ module Sequel
181
181
  end
182
182
 
183
183
  # support reversible create_enum statements if the migration extension is loaded
184
+ # :nocov:
184
185
  if defined?(MigrationReverser)
186
+ # :nocov:
185
187
  class MigrationReverser
186
188
  private
187
189
  def create_enum(name, _)
@@ -83,7 +83,7 @@ module Sequel
83
83
  # If convert_infinite_timestamps is true and the value is infinite, return an appropriate
84
84
  # value based on the convert_infinite_timestamps setting.
85
85
  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])
86
+ if value.is_a?(String) && (m = /((?:[-+]\d\d:\d\d)(:\d\d)?)?( BC)?\z/.match(value)) && (m[2] || m[3])
87
87
  if m[3]
88
88
  value = value.sub(' BC', '').sub(' ', ' BC ')
89
89
  conv = defined?(JRUBY_VERSION) && JRUBY_VERSION == '9.2.0.0'
@@ -189,7 +189,7 @@ module Sequel
189
189
  if date < DATETIME_YEAR_1
190
190
  date <<= ((date.year) * 24 - 12)
191
191
  date = db.from_application_timestamp(date)
192
- minutes = (date.is_a?(DateTime) ? date.offset * 1440 : date.utc_offset/60).to_i
192
+ minutes = (date.offset * 1440).to_i
193
193
  date.strftime("'%Y-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*minutes.divmod(60))} BC'")
194
194
  else
195
195
  super
@@ -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
  #
@@ -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.
@@ -312,7 +362,9 @@ module Sequel
312
362
  end
313
363
  end
314
364
 
365
+ # :nocov:
315
366
  if defined?(HStore)
367
+ # :nocov:
316
368
  class HStore
317
369
  # Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
318
370
  # hstore functions and operators with it.
@@ -354,7 +406,7 @@ end
354
406
  if defined?(Sequel::CoreRefinements)
355
407
  module Sequel::CoreRefinements
356
408
  refine Symbol do
357
- include Sequel::Postgres::HStoreOpMethods
409
+ send INCLUDE_METH, Sequel::Postgres::HStoreOpMethods
358
410
  end
359
411
  end
360
412
  end
@@ -41,7 +41,9 @@ module Sequel
41
41
  db.instance_exec do
42
42
  extend_datasets(InetDatasetMethods)
43
43
 
44
+ # :nocov:
44
45
  if !defined?(SEQUEL_PG_VERSION_INTEGER) || SEQUEL_PG_VERSION_INTEGER >= 11300
46
+ # :nocov:
45
47
  # sequel_pg 1.13.0+ will use inet/cidr conversion procs, but doing so is
46
48
  # slower, so don't add the conversion procs if using sequel_pg 1.13.0+.
47
49
  meth = IPAddr.method(:new)
@@ -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
@@ -387,11 +387,9 @@ module Sequel
387
387
  # argument is true), or a String, Numeric, true, false, or nil
388
388
  # if the json library used supports that.
389
389
  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
390
+ Sequel.parse_json(s)
391
+ rescue Sequel.json_parser_error_class => e
392
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
395
393
  end
396
394
 
397
395
  # Wrap the parsed JSON value in the appropriate JSON wrapper class.