sequel 3.46.0 → 3.47.0

Sign up to get free protection for your applications and to get access to all the features.
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