sequel 3.46.0 → 3.47.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +96 -0
  3. data/Rakefile +7 -1
  4. data/bin/sequel +6 -4
  5. data/doc/active_record.rdoc +1 -1
  6. data/doc/advanced_associations.rdoc +14 -35
  7. data/doc/association_basics.rdoc +66 -4
  8. data/doc/migration.rdoc +4 -0
  9. data/doc/opening_databases.rdoc +6 -0
  10. data/doc/postgresql.rdoc +302 -0
  11. data/doc/release_notes/3.47.0.txt +270 -0
  12. data/doc/security.rdoc +6 -0
  13. data/lib/sequel/adapters/ibmdb.rb +9 -9
  14. data/lib/sequel/adapters/jdbc.rb +22 -7
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -2
  16. data/lib/sequel/adapters/mock.rb +2 -0
  17. data/lib/sequel/adapters/postgres.rb +44 -13
  18. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  20. data/lib/sequel/adapters/shared/postgres.rb +94 -55
  21. data/lib/sequel/adapters/shared/sqlite.rb +3 -1
  22. data/lib/sequel/adapters/sqlite.rb +2 -2
  23. data/lib/sequel/adapters/utils/pg_types.rb +1 -14
  24. data/lib/sequel/adapters/utils/split_alter_table.rb +3 -3
  25. data/lib/sequel/connection_pool/threaded.rb +1 -1
  26. data/lib/sequel/core.rb +1 -1
  27. data/lib/sequel/database/connecting.rb +2 -2
  28. data/lib/sequel/database/features.rb +5 -0
  29. data/lib/sequel/database/misc.rb +47 -5
  30. data/lib/sequel/database/query.rb +2 -2
  31. data/lib/sequel/dataset/actions.rb +4 -2
  32. data/lib/sequel/dataset/misc.rb +1 -1
  33. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  34. data/lib/sequel/dataset/query.rb +8 -6
  35. data/lib/sequel/dataset/sql.rb +8 -6
  36. data/lib/sequel/extensions/constraint_validations.rb +5 -2
  37. data/lib/sequel/extensions/migration.rb +10 -8
  38. data/lib/sequel/extensions/pagination.rb +3 -0
  39. data/lib/sequel/extensions/pg_array.rb +85 -25
  40. data/lib/sequel/extensions/pg_hstore.rb +8 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +4 -1
  42. data/lib/sequel/extensions/pg_inet.rb +16 -13
  43. data/lib/sequel/extensions/pg_interval.rb +6 -2
  44. data/lib/sequel/extensions/pg_json.rb +18 -11
  45. data/lib/sequel/extensions/pg_range.rb +17 -2
  46. data/lib/sequel/extensions/pg_range_ops.rb +7 -5
  47. data/lib/sequel/extensions/pg_row.rb +29 -12
  48. data/lib/sequel/extensions/pretty_table.rb +3 -0
  49. data/lib/sequel/extensions/query.rb +3 -0
  50. data/lib/sequel/extensions/schema_caching.rb +2 -0
  51. data/lib/sequel/extensions/schema_dumper.rb +3 -1
  52. data/lib/sequel/extensions/select_remove.rb +3 -0
  53. data/lib/sequel/model.rb +8 -2
  54. data/lib/sequel/model/associations.rb +39 -27
  55. data/lib/sequel/model/base.rb +99 -38
  56. data/lib/sequel/model/plugins.rb +25 -0
  57. data/lib/sequel/plugins/association_autoreloading.rb +27 -22
  58. data/lib/sequel/plugins/association_dependencies.rb +1 -7
  59. data/lib/sequel/plugins/auto_validations.rb +110 -0
  60. data/lib/sequel/plugins/boolean_readers.rb +1 -6
  61. data/lib/sequel/plugins/caching.rb +6 -13
  62. data/lib/sequel/plugins/class_table_inheritance.rb +1 -0
  63. data/lib/sequel/plugins/composition.rb +14 -7
  64. data/lib/sequel/plugins/constraint_validations.rb +2 -13
  65. data/lib/sequel/plugins/defaults_setter.rb +1 -6
  66. data/lib/sequel/plugins/dirty.rb +8 -0
  67. data/lib/sequel/plugins/error_splitter.rb +54 -0
  68. data/lib/sequel/plugins/force_encoding.rb +1 -5
  69. data/lib/sequel/plugins/hook_class_methods.rb +1 -6
  70. data/lib/sequel/plugins/input_transformer.rb +79 -0
  71. data/lib/sequel/plugins/instance_filters.rb +7 -1
  72. data/lib/sequel/plugins/instance_hooks.rb +7 -1
  73. data/lib/sequel/plugins/json_serializer.rb +5 -10
  74. data/lib/sequel/plugins/lazy_attributes.rb +20 -7
  75. data/lib/sequel/plugins/list.rb +1 -6
  76. data/lib/sequel/plugins/many_through_many.rb +1 -2
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +23 -39
  78. data/lib/sequel/plugins/optimistic_locking.rb +1 -5
  79. data/lib/sequel/plugins/pg_row.rb +4 -2
  80. data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -7
  81. data/lib/sequel/plugins/prepared_statements.rb +1 -5
  82. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -11
  83. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  84. data/lib/sequel/plugins/serialization.rb +11 -13
  85. data/lib/sequel/plugins/serialization_modification_detection.rb +13 -1
  86. data/lib/sequel/plugins/single_table_inheritance.rb +4 -4
  87. data/lib/sequel/plugins/static_cache.rb +67 -19
  88. data/lib/sequel/plugins/string_stripper.rb +7 -27
  89. data/lib/sequel/plugins/subclasses.rb +3 -5
  90. data/lib/sequel/plugins/tactical_eager_loading.rb +2 -2
  91. data/lib/sequel/plugins/timestamps.rb +2 -7
  92. data/lib/sequel/plugins/touch.rb +5 -8
  93. data/lib/sequel/plugins/tree.rb +1 -6
  94. data/lib/sequel/plugins/typecast_on_load.rb +1 -5
  95. data/lib/sequel/plugins/update_primary_key.rb +26 -14
  96. data/lib/sequel/plugins/validation_class_methods.rb +31 -16
  97. data/lib/sequel/plugins/validation_helpers.rb +50 -26
  98. data/lib/sequel/plugins/xml_serializer.rb +3 -6
  99. data/lib/sequel/sql.rb +1 -1
  100. data/lib/sequel/version.rb +1 -1
  101. data/spec/adapters/postgres_spec.rb +131 -15
  102. data/spec/adapters/sqlite_spec.rb +1 -1
  103. data/spec/core/connection_pool_spec.rb +16 -17
  104. data/spec/core/database_spec.rb +111 -40
  105. data/spec/core/dataset_spec.rb +65 -74
  106. data/spec/core/expression_filters_spec.rb +6 -5
  107. data/spec/core/object_graph_spec.rb +0 -1
  108. data/spec/core/schema_spec.rb +23 -23
  109. data/spec/core/spec_helper.rb +5 -1
  110. data/spec/extensions/association_dependencies_spec.rb +1 -1
  111. data/spec/extensions/association_proxies_spec.rb +1 -1
  112. data/spec/extensions/auto_validations_spec.rb +90 -0
  113. data/spec/extensions/caching_spec.rb +6 -0
  114. data/spec/extensions/class_table_inheritance_spec.rb +8 -1
  115. data/spec/extensions/composition_spec.rb +12 -5
  116. data/spec/extensions/constraint_validations_spec.rb +4 -4
  117. data/spec/extensions/core_refinements_spec.rb +29 -79
  118. data/spec/extensions/dirty_spec.rb +14 -0
  119. data/spec/extensions/error_splitter_spec.rb +18 -0
  120. data/spec/extensions/identity_map_spec.rb +0 -1
  121. data/spec/extensions/input_transformer_spec.rb +54 -0
  122. data/spec/extensions/instance_filters_spec.rb +6 -0
  123. data/spec/extensions/instance_hooks_spec.rb +12 -1
  124. data/spec/extensions/json_serializer_spec.rb +0 -1
  125. data/spec/extensions/lazy_attributes_spec.rb +64 -55
  126. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  127. data/spec/extensions/many_through_many_spec.rb +3 -4
  128. data/spec/extensions/many_to_one_pk_lookup_spec.rb +53 -15
  129. data/spec/extensions/migration_spec.rb +16 -0
  130. data/spec/extensions/null_dataset_spec.rb +1 -1
  131. data/spec/extensions/pg_array_spec.rb +48 -1
  132. data/spec/extensions/pg_hstore_ops_spec.rb +10 -2
  133. data/spec/extensions/pg_hstore_spec.rb +5 -0
  134. data/spec/extensions/pg_inet_spec.rb +5 -0
  135. data/spec/extensions/pg_interval_spec.rb +7 -3
  136. data/spec/extensions/pg_json_spec.rb +6 -1
  137. data/spec/extensions/pg_range_ops_spec.rb +4 -1
  138. data/spec/extensions/pg_range_spec.rb +5 -0
  139. data/spec/extensions/pg_row_plugin_spec.rb +13 -0
  140. data/spec/extensions/pg_row_spec.rb +28 -19
  141. data/spec/extensions/pg_typecast_on_load_spec.rb +6 -1
  142. data/spec/extensions/prepared_statements_associations_spec.rb +1 -1
  143. data/spec/extensions/query_literals_spec.rb +1 -1
  144. data/spec/extensions/rcte_tree_spec.rb +2 -2
  145. data/spec/extensions/schema_spec.rb +2 -2
  146. data/spec/extensions/serialization_modification_detection_spec.rb +8 -0
  147. data/spec/extensions/serialization_spec.rb +15 -1
  148. data/spec/extensions/sharding_spec.rb +1 -1
  149. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  150. data/spec/extensions/static_cache_spec.rb +59 -9
  151. data/spec/extensions/tactical_eager_loading_spec.rb +19 -4
  152. data/spec/extensions/update_primary_key_spec.rb +17 -1
  153. data/spec/extensions/validation_class_methods_spec.rb +25 -0
  154. data/spec/extensions/validation_helpers_spec.rb +59 -3
  155. data/spec/integration/associations_test.rb +5 -5
  156. data/spec/integration/eager_loader_test.rb +32 -63
  157. data/spec/integration/model_test.rb +2 -2
  158. data/spec/integration/plugin_test.rb +88 -56
  159. data/spec/integration/prepared_statement_test.rb +1 -1
  160. data/spec/integration/schema_test.rb +1 -1
  161. data/spec/integration/timezone_test.rb +0 -1
  162. data/spec/integration/transaction_test.rb +0 -1
  163. data/spec/model/association_reflection_spec.rb +1 -1
  164. data/spec/model/associations_spec.rb +106 -84
  165. data/spec/model/base_spec.rb +4 -4
  166. data/spec/model/eager_loading_spec.rb +8 -8
  167. data/spec/model/model_spec.rb +27 -9
  168. data/spec/model/plugins_spec.rb +71 -0
  169. data/spec/model/record_spec.rb +99 -13
  170. metadata +12 -2
@@ -27,9 +27,9 @@ module Sequel::Database::SplitAlterTable
27
27
  op_groups.last << op
28
28
  end
29
29
 
30
- op_groups.each do |ops|
31
- next if ops.empty?
32
- alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
30
+ op_groups.each do |opgs|
31
+ next if opgs.empty?
32
+ alter_table_sql_list(name, opgs).each{|sql| execute_ddl(sql)}
33
33
  remove_cached_schema(name)
34
34
  end
35
35
  end
@@ -49,7 +49,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
49
49
  hold do |c|
50
50
  sync do
51
51
  yield c
52
- @available_connections.each{|c| yield c}
52
+ @available_connections.each{|conn| yield conn}
53
53
  end
54
54
  end
55
55
  end
@@ -369,7 +369,7 @@ module Sequel
369
369
  if rescue_rollback
370
370
  begin
371
371
  pr.call
372
- rescue Sequel::Rollback => e
372
+ rescue Sequel::Rollback
373
373
  nil
374
374
  end
375
375
  else
@@ -37,7 +37,7 @@ module Sequel
37
37
 
38
38
  # Returns the scheme symbol for the Database class.
39
39
  def self.adapter_scheme
40
- @scheme
40
+ @scheme if defined?(@scheme)
41
41
  end
42
42
 
43
43
  # Connects to a database. See Sequel.connect.
@@ -55,7 +55,7 @@ module Sequel
55
55
  c = adapter_class(scheme)
56
56
  uri_options = c.send(:uri_to_options, uri)
57
57
  uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
58
- uri_options.to_a.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
58
+ uri_options.to_a.each{|k,v| uri_options[k] = (defined?(URI::DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI).unescape(v) if v.is_a?(String)}
59
59
  opts = uri_options.merge(opts).merge!(:orig_opts=>opts.dup, :uri=>conn_string, :adapter=>scheme)
60
60
  end
61
61
  when Hash
@@ -53,6 +53,11 @@ module Sequel
53
53
  supports_prepared_transactions? && supports_savepoints?
54
54
  end
55
55
 
56
+ # Whether the database supports schema parsing via Database#schema.
57
+ def supports_schema_parsing?
58
+ respond_to?(:schema_parse_table, true)
59
+ end
60
+
56
61
  # Whether the database and adapter support transaction isolation levels, false by default.
57
62
  def supports_transaction_isolation_levels?
58
63
  false
@@ -18,6 +18,33 @@ module Sequel
18
18
  # have specific support for the database in use.
19
19
  DEFAULT_DATABASE_ERROR_REGEXPS = {}.freeze
20
20
 
21
+ # Mapping of schema type symbols to class or arrays of classes for that
22
+ # symbol.
23
+ SCHEMA_TYPE_CLASSES = {:string=>String, :integer=>Integer, :date=>Date, :datetime=>[Time, DateTime].freeze,
24
+ :time=>Sequel::SQLTime, :boolean=>[TrueClass, FalseClass].freeze, :float=>Float, :decimal=>BigDecimal,
25
+ :blob=>Sequel::SQL::Blob}.freeze
26
+
27
+ # Nested hook Proc; each new hook Proc just wraps the previous one.
28
+ @initialize_hook = Proc.new {|db| }
29
+
30
+ # Register a hook that will be run when a new Database is instantiated. It is
31
+ # called with the new database handle.
32
+ def self.after_initialize(&block)
33
+ raise Error, "must provide block to after_initialize" unless block
34
+ Sequel.synchronize do
35
+ previous = @initialize_hook
36
+ @initialize_hook = Proc.new do |db|
37
+ previous.call(db)
38
+ block.call(db)
39
+ end
40
+ end
41
+ end
42
+
43
+ # Apply an extension to all Database objects created in the future.
44
+ def self.extension(*extensions)
45
+ after_initialize{|db| db.extension(*extensions)}
46
+ end
47
+
21
48
  # Register an extension callback for Database objects. ext should be the
22
49
  # extension name symbol, and mod should either be a Module that the
23
50
  # database is extended with, or a callable object called with the database
@@ -35,6 +62,11 @@ module Sequel
35
62
  Sequel.synchronize{EXTENSIONS[ext] = block}
36
63
  end
37
64
 
65
+ # Run the after_initialize hook for the given +instance+.
66
+ def self.run_after_initialize(instance)
67
+ @initialize_hook.call(instance)
68
+ end
69
+
38
70
  # Converts a uri to an options hash. These options are then passed
39
71
  # to a newly created database object.
40
72
  def self.uri_to_options(uri)
@@ -45,7 +77,7 @@ module Sequel
45
77
  :database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
46
78
  end
47
79
  private_class_method :uri_to_options
48
-
80
+
49
81
  # The options hash for this database
50
82
  attr_reader :opts
51
83
 
@@ -93,10 +125,14 @@ module Sequel
93
125
  @dataset_class = dataset_class_default
94
126
  @cache_schema = typecast_value_boolean(@opts.fetch(:cache_schema, true))
95
127
  @dataset_modules = []
128
+ @schema_type_classes = SCHEMA_TYPE_CLASSES.dup
96
129
  self.sql_log_level = @opts[:sql_log_level] ? @opts[:sql_log_level].to_sym : :info
97
130
  @pool = ConnectionPool.get_pool(self, @opts)
98
131
 
99
- Sequel.synchronize{::Sequel::DATABASES.push(self)}
132
+ unless typecast_value_boolean(@opts[:keep_reference]) == false
133
+ Sequel.synchronize{::Sequel::DATABASES.push(self)}
134
+ end
135
+ Sequel::Database.run_after_initialize(self)
100
136
  end
101
137
 
102
138
  # If a transaction is not currently in process, yield to the block immediately.
@@ -201,6 +237,11 @@ module Sequel
201
237
  def quote_identifier(v)
202
238
  schema_utility_dataset.quote_identifier(v)
203
239
  end
240
+
241
+ # Return ruby class or array of classes for the given type symbol.
242
+ def schema_type_class(type)
243
+ @schema_type_classes[type]
244
+ end
204
245
 
205
246
  # Default serial primary key options, used by the table creation
206
247
  # code.
@@ -210,6 +251,7 @@ module Sequel
210
251
 
211
252
  # Cache the prepared statement object at the given name.
212
253
  def set_prepared_statement(name, ps)
254
+ ps.prepared_sql
213
255
  Sequel.synchronize{prepared_statements[name] = ps}
214
256
  end
215
257
 
@@ -306,8 +348,8 @@ module Sequel
306
348
  return klass
307
349
  end
308
350
  else
309
- database_error_regexps.each do |regexp, klass|
310
- return klass if exception.message =~ regexp
351
+ database_error_regexps.each do |regexp, klss|
352
+ return klss if exception.message =~ regexp
311
353
  end
312
354
  end
313
355
 
@@ -349,7 +391,7 @@ module Sequel
349
391
  raise exception
350
392
  end
351
393
  end
352
-
394
+
353
395
  # Typecast the value to an SQL::Blob
354
396
  def typecast_value_blob(value)
355
397
  value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
@@ -154,7 +154,7 @@ module Sequel
154
154
  # # :db_type=>"text",
155
155
  # # :allow_null=>false}]]
156
156
  def schema(table, opts={})
157
- raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
157
+ raise(Error, 'schema parsing is not implemented on this database') unless supports_schema_parsing?
158
158
 
159
159
  opts = opts.dup
160
160
  tab = if table.is_a?(Dataset)
@@ -312,7 +312,7 @@ module Sequel
312
312
  # for this database. Used when parsing metadata so that column symbols are
313
313
  # returned as expected.
314
314
  def metadata_dataset
315
- return @metadata_dataset if @metadata_dataset
315
+ return @metadata_dataset if defined?(@metadata_dataset) && @metadata_dataset
316
316
  ds = dataset
317
317
  ds.identifier_input_method = identifier_input_method_default
318
318
  ds.identifier_output_method = identifier_output_method_default
@@ -148,7 +148,7 @@ module Sequel
148
148
  def each
149
149
  if @opts[:graph]
150
150
  graph_each{|r| yield r}
151
- elsif row_proc = @row_proc
151
+ elsif defined?(@row_proc) && (row_proc = @row_proc)
152
152
  fetch_rows(select_sql){|r| yield row_proc.call(r)}
153
153
  else
154
154
  fetch_rows(select_sql){|r| yield r}
@@ -262,7 +262,9 @@ module Sequel
262
262
  end
263
263
 
264
264
  if column.is_a?(Array)
265
- ds.single_record.values_at(*column.map{|c| hash_key_symbol(c)})
265
+ if r = ds.single_record
266
+ r.values_at(*column.map{|c| hash_key_symbol(c)})
267
+ end
266
268
  else
267
269
  ds.single_value
268
270
  end
@@ -81,7 +81,7 @@ module Sequel
81
81
  when SQL::AliasedExpression
82
82
  s.aliaz
83
83
  when Symbol
84
- sch, table, aliaz = split_symbol(s)
84
+ _, _, aliaz = split_symbol(s)
85
85
  aliaz ? aliaz.to_sym : s
86
86
  else
87
87
  s
@@ -29,7 +29,7 @@ module Sequel
29
29
  # Override the given *_sql method based on the type, and
30
30
  # cache the result of the sql.
31
31
  def prepared_sql
32
- return @prepared_sql if @prepared_sql
32
+ return @prepared_sql if defined?(@prepared_sql) && @prepared_sql
33
33
  @prepared_args ||= []
34
34
  @prepared_sql = super
35
35
  @opts[:sql] = @prepared_sql
@@ -495,7 +495,7 @@ module Sequel
495
495
  using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
496
496
  if using_join && !supports_join_using?
497
497
  h = {}
498
- expr.each{|s| h[s] = s}
498
+ expr.each{|e| h[e] = e}
499
499
  return join_table(type, table, h, options)
500
500
  end
501
501
 
@@ -600,11 +600,14 @@ module Sequel
600
600
  end
601
601
 
602
602
  # Returns a cloned dataset with the given lock style. If style is a
603
- # string, it will be used directly. Otherwise, a symbol may be used
604
- # for database independent locking. Currently :update is respected
605
- # by most databases, and :share is supported by some.
603
+ # string, it will be used directly. You should never pass a string
604
+ # to this method that is derived from user input, as that can lead to
605
+ # SQL injection.
606
606
  #
607
- # DB[:items].lock_style('FOR SHARE') # SELECT * FROM items FOR SHARE
607
+ # A symbol may be used for database independent locking behavior, but
608
+ # all supported symbols have separate methods (e.g. for_update).
609
+ #
610
+ # DB[:items].lock_style('FOR SHARE NOWAIT') # SELECT * FROM items FOR SHARE NOWAIT
608
611
  def lock_style(style)
609
612
  clone(:lock => style)
610
613
  end
@@ -1107,7 +1110,6 @@ module Sequel
1107
1110
  # DB[:items].invert_order([:category, Sequel.desc(:price)]) #=> [Sequel.desc(:category), Sequel.asc(:price)]
1108
1111
  def invert_order(order)
1109
1112
  return nil unless order
1110
- new_order = []
1111
1113
  order.map do |f|
1112
1114
  case f
1113
1115
  when SQL::OrderedExpression
@@ -150,7 +150,9 @@ module Sequel
150
150
  else
151
151
  check_truncation_allowed!
152
152
  raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where] || opts[:having]
153
- _truncate_sql(source_list(opts[:from]))
153
+ t = ''
154
+ source_list_append(t, opts[:from])
155
+ _truncate_sql(t)
154
156
  end
155
157
  end
156
158
 
@@ -345,7 +347,7 @@ module Sequel
345
347
  end
346
348
  end
347
349
  def_append_methods(PUBLIC_APPEND_METHODS + PRIVATE_APPEND_METHODS)
348
- private *PRIVATE_APPEND_METHODS
350
+ private(*PRIVATE_APPEND_METHODS)
349
351
 
350
352
  # SQL fragment for AliasedExpression
351
353
  def aliased_expression_sql_append(sql, ae)
@@ -412,11 +414,11 @@ module Sequel
412
414
  when *IS_OPERATORS
413
415
  r = args.at(1)
414
416
  if r.nil? || supports_is_true?
415
- raise(InvalidOperation, 'Invalid argument used for IS operator') unless v = IS_LITERALS[r]
417
+ raise(InvalidOperation, 'Invalid argument used for IS operator') unless val = IS_LITERALS[r]
416
418
  sql << PAREN_OPEN
417
419
  literal_append(sql, args.at(0))
418
420
  sql << SPACE << op.to_s << SPACE
419
- sql << v << PAREN_CLOSE
421
+ sql << val << PAREN_CLOSE
420
422
  elsif op == :IS
421
423
  complex_expression_sql_append(sql, :"=", args)
422
424
  else
@@ -667,7 +669,7 @@ module Sequel
667
669
  sch = sch.to_s if sch
668
670
  case table_name
669
671
  when Symbol
670
- s, t, a = split_symbol(table_name)
672
+ s, t, _ = split_symbol(table_name)
671
673
  [s||sch, t]
672
674
  when SQL::QualifiedIdentifier
673
675
  [table_name.table.to_s, table_name.column.to_s]
@@ -1195,7 +1197,7 @@ module Sequel
1195
1197
  # name isn't already qualified.
1196
1198
  def qualified_column_name(column, table)
1197
1199
  if Symbol === column
1198
- c_table, column, c_alias = split_symbol(column)
1200
+ c_table, column, _ = split_symbol(column)
1199
1201
  unless c_table
1200
1202
  case table
1201
1203
  when Symbol
@@ -357,7 +357,7 @@ module Sequel
357
357
  when :includes
358
358
  generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
359
359
  if arg.is_a?(Range)
360
- if (b = arg.begin).is_a?(Integer) && (e = arg.end).is_a?(Integer)
360
+ if arg.begin.is_a?(Integer) && arg.end.is_a?(Integer)
361
361
  validation_type = :includes_int_range
362
362
  arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
363
363
  else
@@ -410,7 +410,10 @@ module Sequel
410
410
  # Add the constraint to the generator, including a NOT NULL constraint
411
411
  # for all columns unless the :allow_nil option is given.
412
412
  def generator_add_constraint_from_validation(generator, val, cons)
413
- unless val[:allow_nil]
413
+ if val[:allow_nil]
414
+ nil_cons = Sequel.expr(val[:columns].map{|c| [c, nil]})
415
+ cons = Sequel.|(nil_cons, cons) if cons
416
+ else
414
417
  nil_cons = Sequel.negate(val[:columns].map{|c| [c, nil]})
415
418
  cons = cons ? Sequel.&(nil_cons, cons) : nil_cons
416
419
  end
@@ -85,7 +85,7 @@ module Sequel
85
85
  # Whether to use transactions for this migration, default depends on the
86
86
  # database.
87
87
  attr_accessor :use_transactions
88
-
88
+
89
89
  # Don't set transaction use by default.
90
90
  def initialize
91
91
  @use_transactions = nil
@@ -374,11 +374,12 @@ module Sequel
374
374
  end
375
375
 
376
376
  # Migrates the supplied database using the migration files in the the specified directory. Options:
377
- # * :column :: The column in the :table argument storing the migration version (default: :version).
378
- # * :current :: The current version of the database. If not given, it is retrieved from the database
379
- # using the :table and :column options.
380
- # * :table :: The table containing the schema version (default: :schema_info).
381
- # * :target :: The target version to which to migrate. If not given, migrates to the maximum version.
377
+ # :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
378
+ # :column :: The column in the :table argument storing the migration version (default: :version).
379
+ # :current :: The current version of the database. If not given, it is retrieved from the database
380
+ # using the :table and :column options.
381
+ # :table :: The table containing the schema version (default: :schema_info).
382
+ # :target :: The target version to which to migrate. If not given, migrates to the maximum version.
382
383
  #
383
384
  # Examples:
384
385
  # Sequel::Migrator.run(DB, "migrations")
@@ -435,6 +436,7 @@ module Sequel
435
436
  raise(Error, "Must supply a valid migration path") unless File.directory?(directory)
436
437
  @db = db
437
438
  @directory = directory
439
+ @allow_missing_migration_files = opts[:allow_missing_migration_files]
438
440
  @files = get_migration_files
439
441
  schema, table = @db.send(:schema_and_table, opts[:table] || self.class.const_get(:DEFAULT_SCHEMA_TABLE))
440
442
  @table = schema ? Sequel::SQL::QualifiedIdentifier.new(schema, table) : table
@@ -553,7 +555,7 @@ module Sequel
553
555
  raise(Error, "Duplicate migration version: #{version}") if files[version]
554
556
  files[version] = File.join(directory, file)
555
557
  end
556
- 1.upto(files.length - 1){|i| raise(Error, "Missing migration version: #{i}") unless files[i]}
558
+ 1.upto(files.length - 1){|i| raise(Error, "Missing migration version: #{i}") unless files[i]} unless @allow_missing_migration_files
557
559
  files
558
560
  end
559
561
 
@@ -674,7 +676,7 @@ module Sequel
674
676
  def get_applied_migrations
675
677
  am = ds.select_order_map(column)
676
678
  missing_migration_files = am - files.map{|f| File.basename(f).downcase}
677
- raise(Error, "Applied migration files not in file system: #{missing_migration_files.join(', ')}") if missing_migration_files.length > 0
679
+ raise(Error, "Applied migration files not in file system: #{missing_migration_files.join(', ')}") if missing_migration_files.length > 0 && !@allow_missing_migration_files
678
680
  am
679
681
  end
680
682
 
@@ -103,4 +103,7 @@ module Sequel
103
103
  end
104
104
  end
105
105
  end
106
+
107
+ Database.register_extension(:pagination){}
108
+ Dataset.register_extension(:pagination){}
106
109
  end
@@ -17,8 +17,8 @@
17
17
  #
18
18
  # Sequel.pg_array(array)
19
19
  #
20
- # If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
21
- # or you have loaded the {core_refinements extension}[link:files/doc/core_refinements_rdoc.html])
20
+ # If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html],
21
+ # or you have loaded the {core_refinements extension}[link:files/doc/core_refinements_rdoc.html]
22
22
  # and have activated refinements for the file, you can also use Array#pg_array:
23
23
  #
24
24
  # array.pg_array
@@ -32,15 +32,14 @@
32
32
  #
33
33
  # DB[:table].insert(:column=>Sequel.pg_array([1, 2, 3]))
34
34
  #
35
- # If you would like to use PostgreSQL arrays in your model objects, you
36
- # probably want to modify the schema parsing/typecasting so that it
37
- # recognizes and correctly handles the arrays, which you can do by:
35
+ # To use this extension, first load it into your Sequel::Database instance:
38
36
  #
39
37
  # DB.extension :pg_array
40
38
  #
41
- # If you are not using the native postgres adapter, you probably
42
- # also want to use the typecast_on_load plugin in the model, and
43
- # set it to typecast the array column(s) on load.
39
+ # If you are not using the native postgres adapter and are using array
40
+ # types as model column values you probably should use the
41
+ # pg_typecast_on_load plugin in the model, and set it to typecast the
42
+ # array column(s) on load.
44
43
  #
45
44
  # This extension by default includes handlers for array types for
46
45
  # all scalar types that the native postgres adapter handles. It
@@ -48,18 +47,24 @@
48
47
  # general, you just need to make sure that the scalar type is
49
48
  # handled and has the appropriate converter installed in
50
49
  # Sequel::Postgres::PG_TYPES under the appropriate type OID.
51
- # Then you can call Sequel::Postgres::PGArray.register with
52
- # the appropriate arguments to automatically set up a handler
53
- # for the array type.
50
+ # Then you can call
51
+ # Sequel::Postgres::PGArray::DatabaseMethods#register_array_type
52
+ # to automatically set up a handler for the array type. So if you
53
+ # want to support the foo[] type (assuming the foo type is already
54
+ # supported):
54
55
  #
55
- # For example, if you add support for a scalar custom type named
56
- # foo which uses OID 1234, and you want to add support for the
57
- # foo[] type, which uses type OID 4321, you need to do:
56
+ # DB.register_array_type('foo')
57
+ #
58
+ # You can also register array types on a global basis using
59
+ # Sequel::Postgres::PGArray.register. In this case, you'll have
60
+ # to specify the type oids:
58
61
  #
59
62
  # Sequel::Postgres::PGArray.register('foo', :oid=>4321, :scalar_oid=>1234)
60
63
  #
61
- # Sequel::Postgres::PGArray.register has many additional options
62
- # and should be able to handle most PostgreSQL array types.
64
+ # Both Sequel::Postgres::PGArray::DatabaseMethods#register_array_type
65
+ # and Sequel::Postgres::PGArray.register support many options to
66
+ # customize the array type handling. See the Sequel::Postgres::PGArray.register
67
+ # method documentation.
63
68
  #
64
69
  # If you want an easy way to call PostgreSQL array functions and
65
70
  # operators, look into the pg_array_ops extension.
@@ -115,8 +120,8 @@ module Sequel
115
120
  NULL = 'NULL'.freeze
116
121
  QUOTE = '"'.freeze
117
122
 
118
- # Hash of database array type name strings to symbols (e.g. 'double precision' => :float),
119
- # used by the schema parsing.
123
+ # Global hash of database array type name strings to symbols (e.g. 'double precision' => :float),
124
+ # used by the schema parsing for array types registered globally.
120
125
  ARRAY_TYPES = {}
121
126
 
122
127
  # Registers an array type that the extension should handle. Makes a Database instance that
@@ -153,6 +158,8 @@ module Sequel
153
158
  # typecasting method to be created in the database. This should only be used
154
159
  # to alias existing array types. For example, if there is an array type that can be
155
160
  # treated just like an integer array, you can do :typecast_method=>:integer.
161
+ # :typecast_method_map :: The map in which to place the database type string to type symbol mapping.
162
+ # Defaults to ARRAY_TYPES.
156
163
  # :typecast_methods_module :: If given, a module object to add the typecasting method to. Defaults
157
164
  # to DatabaseMethods.
158
165
  #
@@ -163,6 +170,7 @@ module Sequel
163
170
  type = (typecast_method || opts[:type_symbol] || db_type).to_sym
164
171
  type_procs = opts[:type_procs] || PG_TYPES
165
172
  mod = opts[:typecast_methods_module] || DatabaseMethods
173
+ typecast_method_map = opts[:typecast_method_map] || ARRAY_TYPES
166
174
 
167
175
  if converter = opts[:converter]
168
176
  raise Error, "can't provide both a block and :converter option to register" if block
@@ -178,7 +186,7 @@ module Sequel
178
186
  array_type = (opts[:array_type] || db_type).to_s.dup.freeze
179
187
  creator = (opts[:parser] == :json ? JSONCreator : Creator).new(array_type, converter)
180
188
 
181
- ARRAY_TYPES[db_type] = :"#{type}_array"
189
+ typecast_method_map[db_type] = :"#{type}_array"
182
190
 
183
191
  define_array_typecast_method(mod, type, creator, opts.fetch(:scalar_typecast, type)) unless typecast_method
184
192
 
@@ -208,6 +216,22 @@ module Sequel
208
216
  ESCAPE_REPLACEMENT = '\\\\\1'.freeze
209
217
  BLOB_RANGE = 1...-1
210
218
 
219
+ # Create the local hash of database type strings to schema type symbols,
220
+ # used for array types local to this database.
221
+ def self.extended(db)
222
+ db.instance_eval do
223
+ @pg_array_schema_types ||= {}
224
+ copy_conversion_procs([1009, 1007, 1016, 1231, 1022, 1000, 1001, 1182, 1183, 1270, 1005, 1028, 1021, 1014, 1015])
225
+ [:string_array, :integer_array, :decimal_array, :float_array, :boolean_array, :blob_array, :date_array, :time_array, :datetime_array].each do |v|
226
+ @schema_type_classes[v] = PGArray
227
+ end
228
+ end
229
+
230
+ procs = db.conversion_procs
231
+ procs[1115] = Creator.new("timestamp without time zone", procs[1114])
232
+ procs[1185] = Creator.new("timestamp with time zone", procs[1184])
233
+ end
234
+
211
235
  # Handle arrays in bound variables
212
236
  def bound_variable_arg(arg, conn)
213
237
  case arg
@@ -220,13 +244,24 @@ module Sequel
220
244
  end
221
245
  end
222
246
 
223
- # Make the column type detection handle registered array types.
224
- def schema_column_type(db_type)
225
- if (db_type =~ /\A([^(]+)(?:\([^(]+\))?\[\]\z/io) && (type = ARRAY_TYPES[$1])
226
- type
227
- else
228
- super
247
+ # Register a database specific array type. This can be used to support
248
+ # different array types per Database. Use of this method does not
249
+ # affect global state, unlike PGArray.register. See PGArray.register for
250
+ # possible options.
251
+ def register_array_type(db_type, opts={}, &block)
252
+ opts = {:type_procs=>conversion_procs, :typecast_method_map=>@pg_array_schema_types, :typecast_methods_module=>(class << self; self; end)}.merge(opts)
253
+ unless (opts.has_key?(:scalar_oid) || block) && opts.has_key?(:oid)
254
+ array_oid, scalar_oid = from(:pg_type).where(:typname=>db_type.to_s).get([:typarray, :oid])
255
+ opts[:scalar_oid] = scalar_oid unless opts.has_key?(:scalar_oid) || block
256
+ opts[:oid] = array_oid unless opts.has_key?(:oid)
229
257
  end
258
+ PGArray.register(db_type, opts, &block)
259
+ @schema_type_classes[:"#{opts[:typecast_method] || opts[:type_symbol] || db_type}_array"] = PGArray
260
+ end
261
+
262
+ # Return PGArray if this type matches any supported array type.
263
+ def schema_type_class(type)
264
+ super || (ARRAY_TYPES.each_value{|v| return PGArray if type == v}; nil)
230
265
  end
231
266
 
232
267
  private
@@ -247,6 +282,15 @@ module Sequel
247
282
  end
248
283
  end
249
284
 
285
+ # Automatically handle array types for the given named types.
286
+ def convert_named_procs_to_procs(named_procs)
287
+ h = super
288
+ from(:pg_type).where(:oid=>h.keys).select_map([:typname, :oid, :typarray]).each do |name, scalar_oid, array_oid|
289
+ register_array_type(name, :type_procs=>h, :oid=>array_oid.to_i, :scalar_oid=>scalar_oid.to_i)
290
+ end
291
+ h
292
+ end
293
+
250
294
  # Manually override the typecasting for timestamp array types so that
251
295
  # they use the database's timezone instead of the global Sequel
252
296
  # timezone.
@@ -259,6 +303,22 @@ module Sequel
259
303
  procs
260
304
  end
261
305
 
306
+ # Look into both the current database's array schema types and the global
307
+ # array schema types to get the type symbol for the given database type
308
+ # string.
309
+ def pg_array_schema_type(type)
310
+ @pg_array_schema_types[type] || ARRAY_TYPES[type]
311
+ end
312
+
313
+ # Make the column type detection handle registered array types.
314
+ def schema_column_type(db_type)
315
+ if (db_type =~ /\A([^(]+)(?:\([^(]+\))?\[\]\z/io) && (type = pg_array_schema_type($1))
316
+ type
317
+ else
318
+ super
319
+ end
320
+ end
321
+
262
322
  # Given a value to typecast and the type of PGArray subclass:
263
323
  # * If given a PGArray with a matching array_type, use it directly.
264
324
  # * If given a PGArray with a different array_type, return a PGArray