sequel 3.36.1 → 3.37.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 (108) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +13 -0
  3. data/bin/sequel +12 -16
  4. data/doc/advanced_associations.rdoc +36 -67
  5. data/doc/association_basics.rdoc +11 -16
  6. data/doc/release_notes/3.37.0.txt +338 -0
  7. data/doc/schema_modification.rdoc +4 -0
  8. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
  10. data/lib/sequel/adapters/mysql2.rb +4 -3
  11. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  12. data/lib/sequel/adapters/postgres.rb +4 -60
  13. data/lib/sequel/adapters/shared/mssql.rb +2 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +0 -5
  15. data/lib/sequel/adapters/shared/postgres.rb +68 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +17 -1
  17. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
  18. data/lib/sequel/adapters/utils/pg_types.rb +76 -0
  19. data/lib/sequel/core.rb +13 -0
  20. data/lib/sequel/database/misc.rb +41 -1
  21. data/lib/sequel/database/schema_generator.rb +23 -10
  22. data/lib/sequel/database/schema_methods.rb +26 -4
  23. data/lib/sequel/dataset/graph.rb +2 -1
  24. data/lib/sequel/dataset/query.rb +62 -2
  25. data/lib/sequel/extensions/_pretty_table.rb +7 -3
  26. data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
  27. data/lib/sequel/extensions/blank.rb +4 -0
  28. data/lib/sequel/extensions/columns_introspection.rb +13 -2
  29. data/lib/sequel/extensions/core_extensions.rb +6 -0
  30. data/lib/sequel/extensions/eval_inspect.rb +158 -0
  31. data/lib/sequel/extensions/inflector.rb +4 -0
  32. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  33. data/lib/sequel/extensions/migration.rb +4 -1
  34. data/lib/sequel/extensions/named_timezones.rb +4 -0
  35. data/lib/sequel/extensions/null_dataset.rb +4 -0
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pg_array.rb +219 -168
  38. data/lib/sequel/extensions/pg_array_ops.rb +7 -2
  39. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
  40. data/lib/sequel/extensions/pg_hstore.rb +3 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
  42. data/lib/sequel/extensions/pg_inet.rb +28 -3
  43. data/lib/sequel/extensions/pg_interval.rb +192 -0
  44. data/lib/sequel/extensions/pg_json.rb +21 -9
  45. data/lib/sequel/extensions/pg_range.rb +487 -0
  46. data/lib/sequel/extensions/pg_range_ops.rb +122 -0
  47. data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
  48. data/lib/sequel/extensions/pretty_table.rb +12 -1
  49. data/lib/sequel/extensions/query.rb +4 -0
  50. data/lib/sequel/extensions/query_literals.rb +6 -6
  51. data/lib/sequel/extensions/schema_dumper.rb +39 -38
  52. data/lib/sequel/extensions/select_remove.rb +4 -0
  53. data/lib/sequel/extensions/server_block.rb +3 -2
  54. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  55. data/lib/sequel/extensions/sql_expr.rb +4 -0
  56. data/lib/sequel/extensions/string_date_time.rb +4 -0
  57. data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
  58. data/lib/sequel/extensions/to_dot.rb +4 -0
  59. data/lib/sequel/model/associations.rb +150 -91
  60. data/lib/sequel/plugins/identity_map.rb +2 -2
  61. data/lib/sequel/plugins/list.rb +1 -0
  62. data/lib/sequel/plugins/many_through_many.rb +33 -32
  63. data/lib/sequel/plugins/nested_attributes.rb +11 -3
  64. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  65. data/lib/sequel/plugins/schema.rb +1 -1
  66. data/lib/sequel/sql.rb +14 -14
  67. data/lib/sequel/version.rb +2 -2
  68. data/spec/adapters/mysql_spec.rb +25 -0
  69. data/spec/adapters/postgres_spec.rb +572 -28
  70. data/spec/adapters/sqlite_spec.rb +16 -1
  71. data/spec/core/database_spec.rb +61 -2
  72. data/spec/core/dataset_spec.rb +92 -0
  73. data/spec/core/expression_filters_spec.rb +12 -0
  74. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  75. data/spec/extensions/boolean_readers_spec.rb +25 -25
  76. data/spec/extensions/eval_inspect_spec.rb +58 -0
  77. data/spec/extensions/json_serializer_spec.rb +0 -6
  78. data/spec/extensions/list_spec.rb +1 -1
  79. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  80. data/spec/extensions/many_through_many_spec.rb +81 -0
  81. data/spec/extensions/nested_attributes_spec.rb +21 -4
  82. data/spec/extensions/pg_array_ops_spec.rb +1 -11
  83. data/spec/extensions/pg_array_spec.rb +181 -90
  84. data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
  85. data/spec/extensions/pg_hstore_spec.rb +1 -3
  86. data/spec/extensions/pg_inet_spec.rb +6 -1
  87. data/spec/extensions/pg_interval_spec.rb +73 -0
  88. data/spec/extensions/pg_json_spec.rb +5 -9
  89. data/spec/extensions/pg_range_ops_spec.rb +49 -0
  90. data/spec/extensions/pg_range_spec.rb +372 -0
  91. data/spec/extensions/pg_statement_cache_spec.rb +1 -2
  92. data/spec/extensions/query_literals_spec.rb +1 -2
  93. data/spec/extensions/schema_dumper_spec.rb +48 -89
  94. data/spec/extensions/serialization_spec.rb +1 -5
  95. data/spec/extensions/server_block_spec.rb +2 -2
  96. data/spec/extensions/spec_helper.rb +12 -2
  97. data/spec/extensions/split_array_nil_spec.rb +24 -0
  98. data/spec/integration/associations_test.rb +4 -4
  99. data/spec/integration/database_test.rb +2 -2
  100. data/spec/integration/dataset_test.rb +4 -4
  101. data/spec/integration/eager_loader_test.rb +6 -6
  102. data/spec/integration/plugin_test.rb +2 -2
  103. data/spec/integration/spec_helper.rb +2 -2
  104. data/spec/model/association_reflection_spec.rb +5 -0
  105. data/spec/model/associations_spec.rb +156 -49
  106. data/spec/model/eager_loading_spec.rb +137 -2
  107. data/spec/model/model_spec.rb +10 -10
  108. metadata +15 -2
@@ -58,7 +58,7 @@ module Sequel
58
58
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
59
59
  opts[:eager_loader] = lambda do |eo|
60
60
  return el.call(eo) unless model.identity_map
61
- h = eo[:key_hash][left_pk]
61
+ h = eo[:id_map]
62
62
  eo[:rows].each{|object| object.associations[name] = []}
63
63
  r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
64
64
  l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
@@ -107,7 +107,7 @@ module Sequel
107
107
  left_key_alias = opts[:left_key_alias]
108
108
  opts[:eager_loader] = lambda do |eo|
109
109
  return el.call(eo) unless model.identity_map
110
- h = eo[:key_hash][left_pk]
110
+ h = eo[:id_map]
111
111
  eo[:rows].each{|object| object.associations[name] = []}
112
112
  ds = opts.associated_class
113
113
  opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
@@ -98,6 +98,7 @@ module Sequel
98
98
  unless send(position_field)
99
99
  send("#{position_field}=", list_dataset.max(position_field).to_i+1)
100
100
  end
101
+ super
101
102
  end
102
103
 
103
104
  # Find the last position in the list containing this instance.
@@ -53,7 +53,7 @@ module Sequel
53
53
  self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
54
54
  end
55
55
 
56
- %w'associated_key_table eager_loading_predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
56
+ %w'associated_key_table predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
57
57
  class_eval(<<-END, __FILE__, __LINE__+1)
58
58
  def #{meth}
59
59
  cached_fetch(:#{meth}){calculate_edges[:#{meth}]}
@@ -87,7 +87,7 @@ module Sequel
87
87
 
88
88
  # Transform the :through option into a list of edges and reverse edges to use to join tables when loading the association.
89
89
  def calculate_edges
90
- es = [{:left_table=>self[:model].table_name, :left_key=>self[:left_primary_key]}]
90
+ es = [{:left_table=>self[:model].table_name, :left_key=>self[:left_primary_key_column]}]
91
91
  self[:through].each do |t|
92
92
  es.last.merge!(:right_key=>t[:left], :right_table=>t[:table], :join_type=>t[:join_type]||self[:graph_join_type], :conditions=>(t[:conditions]||[]).to_a, :block=>t[:block])
93
93
  es.last[:only_conditions] = t[:only_conditions] if t.include?(:only_conditions)
@@ -109,7 +109,7 @@ module Sequel
109
109
  :final_reverse_edge=>final_reverse_edge,
110
110
  :edges=>edges,
111
111
  :reverse_edges=>reverse_edges,
112
- :eager_loading_predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
112
+ :predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
113
113
  :associated_key_table=>final_reverse_edge[:alias],
114
114
  }
115
115
  h.each{|k, v| cached_set(k, v)}
@@ -123,24 +123,17 @@ module Sequel
123
123
  # * through - The tables and keys to join between the current table and the associated table.
124
124
  # Must be an array, with elements that are either 3 element arrays, or hashes with keys :table, :left, and :right.
125
125
  # The required entries in the array/hash are:
126
- # * :table (first array element) - The name of the table to join.
127
- # * :left (middle array element) - The key joining the table to the previous table. Can use an
128
- # array of symbols for a composite key association.
129
- # * :right (last array element) - The key joining the table to the next table. Can use an
130
- # array of symbols for a composite key association.
126
+ # :table (first array element) :: The name of the table to join.
127
+ # :left (middle array element) :: The key joining the table to the previous table. Can use an
128
+ # array of symbols for a composite key association.
129
+ # :right (last array element) :: The key joining the table to the next table. Can use an
130
+ # array of symbols for a composite key association.
131
131
  # If a hash is provided, the following keys are respected when using eager_graph:
132
- # * :block - A proc to use as the block argument to join.
133
- # * :conditions - Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
134
- # * :join_type - The join type to use for the join, defaults to :left_outer.
135
- # * :only_conditions - Conditions to use for the join instead of the ones specified by the keys.
136
- # * opts - The options for the associaion. Takes the same options as associate, and supports these additional options:
137
- # * :left_primary_key - column in current table that the first :left option in
138
- # through points to, as a symbol. Defaults to primary key of current table. Can use an
139
- # array of symbols for a composite key association.
140
- # * :right_primary_key - column in associated table that the final :right option in
141
- # through points to, as a symbol. Defaults to primary key of the associated table. Can use an
142
- # array of symbols for a composite key association.
143
- # * :uniq - Adds a after_load callback that makes the array of objects unique.
132
+ # :block :: A proc to use as the block argument to join.
133
+ # :conditions :: Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
134
+ # :join_type :: The join type to use for the join, defaults to :left_outer.
135
+ # :only_conditions :: Conditions to use for the join instead of the ones specified by the keys.
136
+ # * opts - The options for the associaion. Takes the same options as many_to_many.
144
137
  def many_through_many(name, through, opts={}, &block)
145
138
  associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
146
139
  end
@@ -171,23 +164,26 @@ module Sequel
171
164
  uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
172
165
  left_keys = Array(left_key)
173
166
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
167
+ opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
174
168
  left_pks = opts[:left_primary_keys] = Array(left_pk)
169
+ lpkc = opts[:left_primary_key_column] ||= left_pk
170
+ lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
175
171
  opts[:dataset] ||= lambda do
176
172
  ds = opts.associated_class
177
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
173
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
178
174
  ft = opts.final_reverse_edge
179
- ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
175
+ ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + opts.predicate_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias], :qualify=>:deep)
180
176
  end
181
177
 
182
178
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
183
179
  opts[:eager_loader] ||= lambda do |eo|
184
- h = eo[:key_hash][left_pk]
180
+ h = eo[:id_map]
185
181
  rows = eo[:rows]
186
182
  rows.each{|object| object.associations[name] = []}
187
183
  ds = opts.associated_class
188
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
184
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
189
185
  ft = opts.final_reverse_edge
190
- ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.eager_loading_predicate_key, h.keys]], :table_alias=>ft[:alias])
186
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
191
187
  ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
192
188
  case opts.eager_limit_strategy
193
189
  when :window_function
@@ -198,7 +194,7 @@ module Sequel
198
194
  ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
199
195
  dsa = ds.send(:dataset_alias, 2)
200
196
  opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
201
- xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa)
197
+ xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa, :qualify=>:deep)
202
198
  end
203
199
  end
204
200
  ds.all do |assoc_record|
@@ -227,11 +223,11 @@ module Sequel
227
223
  ds = eo[:self]
228
224
  iq = eo[:implicit_qualifier]
229
225
  opts.edges.each do |t|
230
- ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
226
+ ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :qualify=>:deep, :implicit_qualifier=>iq, &t[:block])
231
227
  iq = nil
232
228
  end
233
229
  fe = opts.final_edge
234
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
230
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>join_type, &graph_block)
235
231
  end
236
232
 
237
233
  def_association_dataset_methods(opts)
@@ -243,22 +239,27 @@ module Sequel
243
239
 
244
240
  # Use a subquery to filter rows to those related to the given associated object
245
241
  def many_through_many_association_filter_expression(op, ref, obj)
246
- lpks = ref[:left_primary_keys]
242
+ lpks = ref[:left_primary_key_columns]
247
243
  lpks = lpks.first if lpks.length == 1
248
244
  lpks = ref.qualify(model.table_name, lpks)
249
245
  edges = ref.edges
250
246
  first, rest = edges.first, edges[1..-1]
251
247
  last = edges.last
252
248
  ds = model.db[first[:table]].select(*Array(ref.qualify(first[:table], first[:right])))
253
- rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), &e[:block])}
249
+ rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), :qualify=>:deep, &e[:block])}
254
250
  last_alias = if rest.empty?
255
251
  first[:table]
256
252
  else
257
253
  last_join = ds.opts[:join].last
258
254
  last_join.table_alias || last_join.table
259
255
  end
260
- meths = ref.right_primary_keys
261
- meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
256
+
257
+ meths = if obj.is_a?(Sequel::Dataset)
258
+ ref.qualify(obj.model.table_name, ref.right_primary_keys)
259
+ else
260
+ ref.right_primary_key_methods
261
+ end
262
+
262
263
  exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref.final_edge[:left])), meths, obj)
263
264
  if exp == SQL::Constants::FALSE
264
265
  association_filter_handle_inversion(op, exp, Array(lpks))
@@ -83,8 +83,11 @@ module Sequel
83
83
 
84
84
  # Allow nested attributes to be set for the given associations. Options:
85
85
  # * :destroy - Allow destruction of nested records.
86
- # * :fields - If provided, should be an Array. Restricts the fields allowed to be
87
- # modified through the association_attributes= method to the specific fields given.
86
+ # * :fields - If provided, should be an Array or proc. If it is an array,
87
+ # restricts the fields allowed to be modified through the
88
+ # association_attributes= method to the specific fields given. If it is
89
+ # a proc, it will be called with the associated object and should return an
90
+ # array of the allowable fields.
88
91
  # * :limit - For *_to_many associations, a limit on the number of records
89
92
  # that will be processed, to prevent denial of service attacks.
90
93
  # * :reject_if - A proc that is given each attribute hash before it is
@@ -168,7 +171,11 @@ module Sequel
168
171
 
169
172
  # Don't need to validate the object twice if :validate association option is not false
170
173
  # and don't want to validate it at all if it is false.
171
- send(reflection[:type] == :many_to_one ? :before_save_hook : :after_save_hook){send(reflection.setter_method, obj.save(:validate=>false))}
174
+ if reflection[:type] == :many_to_one
175
+ before_save_hook{send(reflection.setter_method, obj.save(:validate=>false))}
176
+ else
177
+ after_save_hook{send(reflection.setter_method, obj)}
178
+ end
172
179
  end
173
180
  obj
174
181
  end
@@ -207,6 +214,7 @@ module Sequel
207
214
  # specific :fields if configured.
208
215
  def nested_attributes_set_attributes(reflection, obj, attributes)
209
216
  if fields = reflection[:nested_attributes][:fields]
217
+ fields = fields.call(obj) if fields.respond_to?(:call)
210
218
  obj.set_only(attributes, fields)
211
219
  else
212
220
  obj.set(attributes)
@@ -160,7 +160,7 @@ module Sequel
160
160
  end
161
161
  a[:after_load] ||= aal
162
162
  a[:eager_loader] ||= proc do |eo|
163
- id_map = eo[:key_hash][key]
163
+ id_map = eo[:id_map]
164
164
  parent_map = {}
165
165
  children_map = {}
166
166
  eo[:rows].each do |obj|
@@ -259,7 +259,7 @@ module Sequel
259
259
  end
260
260
  d[:after_load] = dal
261
261
  d[:eager_loader] ||= proc do |eo|
262
- id_map = eo[:key_hash][prkey]
262
+ id_map = eo[:id_map]
263
263
  associations = eo[:associations]
264
264
  parent_map = {}
265
265
  children_map = {}
@@ -66,7 +66,7 @@ module Sequel
66
66
  # test code or simple examples.
67
67
  def set_schema(name = nil, &block)
68
68
  set_dataset(db[name]) if name
69
- @schema = Sequel::Schema::Generator.new(db, &block)
69
+ @schema = db.create_table_generator(&block)
70
70
  set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
71
71
  end
72
72
 
data/lib/sequel/sql.rb CHANGED
@@ -67,7 +67,7 @@ module Sequel
67
67
  # such methods are the values of the object's attributes.
68
68
  def self.attr_reader(*args)
69
69
  super
70
- comparison_attrs.concat args
70
+ comparison_attrs.concat(args)
71
71
  end
72
72
 
73
73
  # All attributes used for equality and hash methods.
@@ -166,12 +166,12 @@ module Sequel
166
166
  # Custom expressions that may have different syntax on different databases
167
167
  CUSTOM_EXPRESSIONS = [:extract]
168
168
 
169
- # An array of args for this object
170
- attr_reader :args
171
-
172
169
  # The operator symbol for this object
173
170
  attr_reader :op
174
171
 
172
+ # An array of args for this object
173
+ attr_reader :args
174
+
175
175
  # Set the operator symbol and arguments for this object to the ones given.
176
176
  # Convert all args that are hashes or arrays of two element arrays to +BooleanExpressions+,
177
177
  # other than the second arg for an IN/NOT IN operator.
@@ -1075,12 +1075,12 @@ module Sequel
1075
1075
 
1076
1076
  # Represents an SQL function call.
1077
1077
  class Function < GenericExpression
1078
- # The array of arguments to pass to the function (may be blank)
1079
- attr_reader :args
1080
-
1081
1078
  # The SQL function to call
1082
1079
  attr_reader :f
1083
1080
 
1081
+ # The array of arguments to pass to the function (may be blank)
1082
+ attr_reader :args
1083
+
1084
1084
  # Set the functions and args to the given arguments
1085
1085
  def initialize(f, *args)
1086
1086
  @f, @args = f, args
@@ -1173,16 +1173,16 @@ module Sequel
1173
1173
  # required for the prepared statement support and for database-specific
1174
1174
  # literalization.
1175
1175
  class PlaceholderLiteralString < GenericExpression
1176
+ # The literal string containing placeholders. This can also be an array
1177
+ # of strings, where each arg in args goes between the string elements.
1178
+ attr_reader :str
1179
+
1176
1180
  # The arguments that will be subsituted into the placeholders.
1177
1181
  # Either an array of unnamed placeholders (which will be substituted in
1178
1182
  # order for ? characters), or a hash of named placeholders (which will be
1179
1183
  # substituted for :key phrases).
1180
1184
  attr_reader :args
1181
1185
 
1182
- # The literal string containing placeholders. This can also be an array
1183
- # of strings, where each arg in args goes between the string elements.
1184
- attr_reader :str
1185
-
1186
1186
  # Whether to surround the expression with parantheses
1187
1187
  attr_reader :parens
1188
1188
 
@@ -1257,12 +1257,12 @@ module Sequel
1257
1257
  class QualifiedIdentifier < GenericExpression
1258
1258
  include QualifyingMethods
1259
1259
 
1260
- # The column/table referenced
1261
- attr_reader :column
1262
-
1263
1260
  # The table/schema qualifying the reference
1264
1261
  attr_reader :table
1265
1262
 
1263
+ # The column/table referenced
1264
+ attr_reader :column
1265
+
1266
1266
  # Set the table and column to the given arguments
1267
1267
  def initialize(table, column)
1268
1268
  @table, @column = table, column
@@ -3,10 +3,10 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 36
6
+ MINOR = 37
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
- TINY = 1
9
+ TINY = 0
10
10
 
11
11
  # The version of Sequel you are using, as a string (e.g. "2.11.0")
12
12
  VERSION = [MAJOR, MINOR, TINY].join('.')
@@ -1224,3 +1224,28 @@ if MYSQL_DB.adapter_scheme == :mysql
1224
1224
  end
1225
1225
  end
1226
1226
  end
1227
+
1228
+ if MYSQL_DB.adapter_scheme == :mysql2
1229
+ describe "Mysql2 streaming" do
1230
+ before(:all) do
1231
+ MYSQL_DB.create_table!(:a){Integer :a}
1232
+ MYSQL_DB.transaction do
1233
+ 1000.times do |i|
1234
+ MYSQL_DB[:a].insert(i)
1235
+ end
1236
+ end
1237
+ @ds = MYSQL_DB[:a].stream.order(:a)
1238
+ end
1239
+ after(:all) do
1240
+ MYSQL_DB.drop_table?(:a)
1241
+ end
1242
+
1243
+ specify "should correctly stream results" do
1244
+ @ds.map(:a).should == (0...1000).to_a
1245
+ end
1246
+
1247
+ specify "should correctly handle early returning when streaming results" do
1248
+ 3.times{@ds.each{|r| break r[:a]}.should == 0}
1249
+ end
1250
+ end if false
1251
+ end
@@ -158,6 +158,43 @@ describe "A PostgreSQL dataset" do
158
158
  @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
159
159
  end
160
160
 
161
+ specify "should support exclusion constraints when creating or altering tables" do
162
+ begin
163
+ @db = POSTGRES_DB
164
+ @db.create_table!(:atest){Integer :t; exclude [[:t.desc(:nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
165
+ @db[:atest].insert(1)
166
+ @db[:atest].insert(2)
167
+ proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
168
+
169
+ @db.create_table!(:atest){Integer :t}
170
+ @db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
171
+ @db[:atest].insert(1)
172
+ @db[:atest].insert(2)
173
+ proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
174
+ @db.alter_table(:atest){drop_constraint 'atest_ex'}
175
+ ensure
176
+ @db.drop_table?(:atest)
177
+ end
178
+ end if POSTGRES_DB.server_version >= 90000
179
+
180
+ specify "should support adding foreign key constarints that are not yet valid, and validating them later" do
181
+ begin
182
+ @db = POSTGRES_DB
183
+ @db.create_table!(:atest){primary_key :id; Integer :fk}
184
+ @db[:atest].insert(1, 5)
185
+ @db.alter_table(:atest){add_foreign_key [:fk], :atest, :not_valid=>true, :name=>:atest_fk}
186
+ @db[:atest].insert(2, 1)
187
+ proc{@db[:atest].insert(3, 4)}.should raise_error(Sequel::DatabaseError)
188
+
189
+ proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should raise_error(Sequel::DatabaseError)
190
+ @db[:atest].where(:id=>1).update(:fk=>2)
191
+ @db.alter_table(:atest){validate_constraint :atest_fk}
192
+ proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should_not raise_error
193
+ ensure
194
+ @db.drop_table?(:atest)
195
+ end
196
+ end if POSTGRES_DB.server_version >= 90200
197
+
161
198
  specify "should support :using when altering a column's type" do
162
199
  begin
163
200
  @db = POSTGRES_DB
@@ -1349,11 +1386,12 @@ end
1349
1386
 
1350
1387
  describe 'PostgreSQL array handling' do
1351
1388
  before(:all) do
1352
- Sequel.extension :pg_array
1353
1389
  @db = POSTGRES_DB
1354
- @db.extend Sequel::Postgres::PGArray::DatabaseMethods
1390
+ @db.extension :pg_array
1355
1391
  @ds = @db[:items]
1356
1392
  @native = POSTGRES_DB.adapter_scheme == :postgres
1393
+ @jdbc = POSTGRES_DB.adapter_scheme == :jdbc
1394
+ @tp = lambda{@db.schema(:items).map{|a| a.last[:type]}}
1357
1395
  end
1358
1396
  after do
1359
1397
  @db.drop_table?(:items)
@@ -1367,21 +1405,29 @@ describe 'PostgreSQL array handling' do
1367
1405
  column :r, 'real[]'
1368
1406
  column :dp, 'double precision[]'
1369
1407
  end
1408
+ @tp.call.should == [:integer_array, :integer_array, :bigint_array, :float_array, :float_array]
1370
1409
  @ds.insert([1].pg_array(:int2), [nil, 2].pg_array(:int4), [3, nil].pg_array(:int8), [4, nil, 4.5].pg_array(:real), [5, nil, 5.5].pg_array("double precision"))
1371
1410
  @ds.count.should == 1
1372
- if @native
1373
- rs = @ds.all
1411
+ rs = @ds.all
1412
+ if @jdbc || @native
1374
1413
  rs.should == [{:i2=>[1], :i4=>[nil, 2], :i8=>[3, nil], :r=>[4.0, nil, 4.5], :dp=>[5.0, nil, 5.5]}]
1414
+ end
1415
+ if @native
1375
1416
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1376
1417
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1377
1418
  @ds.delete
1378
1419
  @ds.insert(rs.first)
1379
1420
  @ds.all.should == rs
1421
+ end
1380
1422
 
1381
- @ds.delete
1382
- @ds.insert([[1], [2]].pg_array(:int2), [[nil, 2], [3, 4]].pg_array(:int4), [[3, nil], [nil, nil]].pg_array(:int8), [[4, nil], [nil, 4.5]].pg_array(:real), [[5, nil], [nil, 5.5]].pg_array("double precision"))
1383
- rs = @ds.all
1423
+ @ds.delete
1424
+ @ds.insert([[1], [2]].pg_array(:int2), [[nil, 2], [3, 4]].pg_array(:int4), [[3, nil], [nil, nil]].pg_array(:int8), [[4, nil], [nil, 4.5]].pg_array(:real), [[5, nil], [nil, 5.5]].pg_array("double precision"))
1425
+
1426
+ rs = @ds.all
1427
+ if @jdbc || @native
1384
1428
  rs.should == [{:i2=>[[1], [2]], :i4=>[[nil, 2], [3, 4]], :i8=>[[3, nil], [nil, nil]], :r=>[[4, nil], [nil, 4.5]], :dp=>[[5, nil], [nil, 5.5]]}]
1429
+ end
1430
+ if @native
1385
1431
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1386
1432
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1387
1433
  @ds.delete
@@ -1394,21 +1440,28 @@ describe 'PostgreSQL array handling' do
1394
1440
  @db.create_table!(:items) do
1395
1441
  column :n, 'numeric[]'
1396
1442
  end
1443
+ @tp.call.should == [:decimal_array]
1397
1444
  @ds.insert([BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')].pg_array(:numeric))
1398
1445
  @ds.count.should == 1
1399
- if @native
1400
- rs = @ds.all
1446
+ rs = @ds.all
1447
+ if @jdbc || @native
1401
1448
  rs.should == [{:n=>[BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')]}]
1449
+ end
1450
+ if @native
1402
1451
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1403
1452
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1404
1453
  @ds.delete
1405
1454
  @ds.insert(rs.first)
1406
1455
  @ds.all.should == rs
1456
+ end
1407
1457
 
1408
- @ds.delete
1409
- @ds.insert([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]].pg_array(:numeric))
1410
- rs = @ds.all
1458
+ @ds.delete
1459
+ @ds.insert([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]].pg_array(:numeric))
1460
+ rs = @ds.all
1461
+ if @jdbc || @native
1411
1462
  rs.should == [{:n=>[[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]]}]
1463
+ end
1464
+ if @native
1412
1465
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1413
1466
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1414
1467
  @ds.delete
@@ -1423,21 +1476,75 @@ describe 'PostgreSQL array handling' do
1423
1476
  column :vc, 'varchar[]'
1424
1477
  column :t, 'text[]'
1425
1478
  end
1479
+ @tp.call.should == [:string_array, :string_array, :string_array]
1426
1480
  @ds.insert(['a', nil, 'NULL', 'b"\'c'].pg_array('char(4)'), ['a', nil, 'NULL', 'b"\'c'].pg_array(:varchar), ['a', nil, 'NULL', 'b"\'c'].pg_array(:text))
1427
1481
  @ds.count.should == 1
1428
- if @native
1429
- rs = @ds.all
1482
+ rs = @ds.all
1483
+ if @jdbc || @native
1430
1484
  rs.should == [{:c=>['a ', nil, 'NULL', 'b"\'c'], :vc=>['a', nil, 'NULL', 'b"\'c'], :t=>['a', nil, 'NULL', 'b"\'c']}]
1485
+ end
1486
+ if @native
1431
1487
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1432
1488
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1433
1489
  @ds.delete
1434
1490
  @ds.insert(rs.first)
1435
1491
  @ds.all.should == rs
1492
+ end
1436
1493
 
1494
+ @ds.delete
1495
+ @ds.insert([[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array('char(4)'), [[['a'], ['']], [['NULL'], ['b"\'c']]].pg_array(:varchar), [[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array(:text))
1496
+ rs = @ds.all
1497
+ if @jdbc || @native
1498
+ rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
1499
+ end
1500
+ if @native
1501
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1502
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1437
1503
  @ds.delete
1438
- @ds.insert([[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array('char(4)'), [[['a'], ['']], [['NULL'], ['b"\'c']]].pg_array(:varchar), [[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array(:text))
1504
+ @ds.insert(rs.first)
1505
+ @ds.all.should == rs
1506
+ end
1507
+ end
1508
+
1509
+ specify 'insert and retrieve arrays of other types' do
1510
+ @db.create_table!(:items) do
1511
+ column :b, 'bool[]'
1512
+ column :d, 'date[]'
1513
+ column :t, 'time[]'
1514
+ column :ts, 'timestamp[]'
1515
+ column :tstz, 'timestamptz[]'
1516
+ end
1517
+ @tp.call.should == [:boolean_array, :date_array, :time_array, :datetime_array, :datetime_timezone_array]
1518
+
1519
+ d = Date.today
1520
+ t = Sequel::SQLTime.create(10, 20, 30)
1521
+ ts = Time.local(2011, 1, 2, 3, 4, 5)
1522
+
1523
+ @ds.insert([true, false].pg_array(:bool), [d, nil].pg_array(:date), [t, nil].pg_array(:time), [ts, nil].pg_array(:timestamp), [ts, nil].pg_array(:timestamptz))
1524
+ @ds.count.should == 1
1525
+ rs = @ds.all
1526
+ if @jdbc || @native
1527
+ rs.should == [{:b=>[true, false], :d=>[d, nil], :t=>[t, nil], :ts=>[ts, nil], :tstz=>[ts, nil]}]
1528
+ end
1529
+ if @native
1530
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1531
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1532
+ @ds.delete
1533
+ @ds.insert(rs.first)
1534
+ @ds.all.should == rs
1535
+ end
1536
+
1537
+ @db.create_table!(:items) do
1538
+ column :ba, 'bytea[]'
1539
+ column :tz, 'timetz[]'
1540
+ column :o, 'oid[]'
1541
+ end
1542
+ @tp.call.should == [:blob_array, :time_timezone_array, :integer_array]
1543
+ @ds.insert( [Sequel.blob("a\0"), nil].pg_array(:bytea), [t, nil].pg_array(:timetz), [1, 2, 3].pg_array(:oid))
1544
+ @ds.count.should == 1
1545
+ if @native
1439
1546
  rs = @ds.all
1440
- rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
1547
+ rs.should == [{:ba=>[Sequel.blob("a\0"), nil], :tz=>[t, nil], :o=>[1, 2, 3]}]
1441
1548
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1442
1549
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1443
1550
  @ds.delete
@@ -1463,6 +1570,42 @@ describe 'PostgreSQL array handling' do
1463
1570
  @ds.get(:i).should == a
1464
1571
  @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1465
1572
  @ds.filter(:i=>:$i).call(:first, :i=>['', nil, nil, 'a']).should == nil
1573
+
1574
+ @db.create_table!(:items) do
1575
+ column :i, 'date[]'
1576
+ end
1577
+ a = [Date.today]
1578
+ @ds.call(:insert, {:i=>:$i}, :i=>a.pg_array('date'))
1579
+ @ds.get(:i).should == a
1580
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1581
+ @ds.filter(:i=>:$i).call(:first, :i=>[Date.today-1].pg_array('date')).should == nil
1582
+
1583
+ @db.create_table!(:items) do
1584
+ column :i, 'timestamp[]'
1585
+ end
1586
+ a = [Time.local(2011, 1, 2, 3, 4, 5)]
1587
+ @ds.call(:insert, {:i=>:$i}, :i=>a.pg_array('timestamp'))
1588
+ @ds.get(:i).should == a
1589
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1590
+ @ds.filter(:i=>:$i).call(:first, :i=>[a.first-1].pg_array('timestamp')).should == nil
1591
+
1592
+ @db.create_table!(:items) do
1593
+ column :i, 'boolean[]'
1594
+ end
1595
+ a = [true, false]
1596
+ @ds.call(:insert, {:i=>:$i}, :i=>a.pg_array('boolean'))
1597
+ @ds.get(:i).should == a
1598
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1599
+ @ds.filter(:i=>:$i).call(:first, :i=>[false, true].pg_array('boolean')).should == nil
1600
+
1601
+ @db.create_table!(:items) do
1602
+ column :i, 'bytea[]'
1603
+ end
1604
+ a = [Sequel.blob("a\0'\"")]
1605
+ @ds.call(:insert, {:i=>:$i}, :i=>a.pg_array('bytea'))
1606
+ @ds.get(:i).should == a
1607
+ @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
1608
+ @ds.filter(:i=>:$i).call(:first, :i=>[Sequel.blob("b\0")].pg_array('bytea')).should == nil
1466
1609
  end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
1467
1610
 
1468
1611
  specify 'with models' do
@@ -1541,9 +1684,8 @@ end
1541
1684
 
1542
1685
  describe 'PostgreSQL hstore handling' do
1543
1686
  before(:all) do
1544
- Sequel.extension :pg_hstore
1545
1687
  @db = POSTGRES_DB
1546
- @db.extend Sequel::Postgres::HStore::DatabaseMethods
1688
+ @db.extension :pg_hstore
1547
1689
  @ds = @db[:items]
1548
1690
  @h = {'a'=>'b', 'c'=>nil, 'd'=>'NULL', 'e'=>'\\\\" \\\' ,=>'}
1549
1691
  @native = POSTGRES_DB.adapter_scheme == :postgres
@@ -1586,20 +1728,104 @@ describe 'PostgreSQL hstore handling' do
1586
1728
  @ds.filter(:i=>:$i).call(:first, :i=>{}).should == nil
1587
1729
  end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
1588
1730
 
1589
- specify 'with models' do
1731
+ specify 'with models and associations' do
1590
1732
  @db.create_table!(:items) do
1591
1733
  primary_key :id
1592
1734
  column :h, :hstore
1593
1735
  end
1594
- c = Class.new(Sequel::Model(@db[:items]))
1736
+ c = Class.new(Sequel::Model(@db[:items])) do
1737
+ def self.name
1738
+ 'Item'
1739
+ end
1740
+ unrestrict_primary_key
1741
+ def item_id
1742
+ h['item_id'].to_i if h
1743
+ end
1744
+ def left_item_id
1745
+ h['left_item_id'].to_i if h
1746
+ end
1747
+ end
1748
+ Sequel.extension :pg_hstore_ops
1749
+ c.plugin :many_through_many
1595
1750
  c.plugin :typecast_on_load, :h unless @native
1596
- c.create(:h=>@h.hstore).h.should == @h
1751
+
1752
+ h = {'item_id'=>"2", 'left_item_id'=>"1"}
1753
+ o2 = c.create(:id=>2)
1754
+ o = c.create(:id=>1, :h=>h)
1755
+ o.h.should == h
1756
+
1757
+ c.many_to_one :item, :class=>c, :key_column=>Sequel.cast(:h.hstore['item_id'], Integer)
1758
+ c.one_to_many :items, :class=>c, :key=>Sequel.cast(:h.hstore['item_id'], Integer), :key_method=>:item_id
1759
+ c.many_to_many :related_items, :class=>c, :join_table=>:items___i, :left_key=>Sequel.cast(:h.hstore['left_item_id'], Integer), :right_key=>Sequel.cast(:h.hstore['item_id'], Integer)
1760
+
1761
+ c.many_to_one :other_item, :class=>c, :key=>:id, :primary_key_method=>:item_id, :primary_key=>Sequel.cast(:h.hstore['item_id'], Integer)
1762
+ c.one_to_many :other_items, :class=>c, :primary_key=>:item_id, :key=>:id, :primary_key_column=>Sequel.cast(:h.hstore['item_id'], Integer)
1763
+ c.many_to_many :other_related_items, :class=>c, :join_table=>:items___i, :left_key=>:id, :right_key=>:id,
1764
+ :left_primary_key_column=>Sequel.cast(:h.hstore['left_item_id'], Integer),
1765
+ :left_primary_key=>:left_item_id,
1766
+ :right_primary_key=>Sequel.cast(:h.hstore['left_item_id'], Integer),
1767
+ :right_primary_key_method=>:left_item_id
1768
+
1769
+ c.many_through_many :mtm_items, [
1770
+ [:items, Sequel.cast(:h.hstore['item_id'], Integer), Sequel.cast(:h.hstore['left_item_id'], Integer)],
1771
+ [:items, Sequel.cast(:h.hstore['left_item_id'], Integer), Sequel.cast(:h.hstore['left_item_id'], Integer)]
1772
+ ],
1773
+ :class=>c,
1774
+ :left_primary_key_column=>Sequel.cast(:h.hstore['item_id'], Integer),
1775
+ :left_primary_key=>:item_id,
1776
+ :right_primary_key=>Sequel.cast(:h.hstore['left_item_id'], Integer),
1777
+ :right_primary_key_method=>:left_item_id
1778
+
1779
+ # Lazily Loading
1780
+ o.item.should == o2
1781
+ o2.items.should == [o]
1782
+ o.related_items.should == [o2]
1783
+ o2.other_item.should == o
1784
+ o.other_items.should == [o2]
1785
+ o.other_related_items.should == [o]
1786
+ o.mtm_items.should == [o]
1787
+
1788
+ # Eager Loading via eager
1789
+ os = c.eager(:item, :related_items, :other_items, :other_related_items, :mtm_items).where(:id=>1).all.first
1790
+ os.item.should == o2
1791
+ os.related_items.should == [o2]
1792
+ os.other_items.should == [o2]
1793
+ os.other_related_items.should == [o]
1794
+ os.mtm_items.should == [o]
1795
+ os = c.eager(:items, :other_item).where(:id=>2).all.first
1796
+ os.items.should == [o]
1797
+ os.other_item.should == o
1798
+
1799
+ # Eager Loading via eager_graph
1800
+ c.eager_graph(:item).where(:items__id=>1).all.first.item.should == o2
1801
+ c.eager_graph(:items).where(:items__id=>2).all.first.items.should == [o]
1802
+ c.eager_graph(:related_items).where(:items__id=>1).all.first.related_items.should == [o2]
1803
+ c.eager_graph(:other_item).where(:items__id=>2).all.first.other_item.should == o
1804
+ c.eager_graph(:other_items).where(:items__id=>1).all.first.other_items.should == [o2]
1805
+ c.eager_graph(:other_related_items).where(:items__id=>1).all.first.other_related_items.should == [o]
1806
+ c.eager_graph(:mtm_items).where(:items__id=>1).all.first.mtm_items.should == [o]
1807
+
1808
+ # Filter By Associations - Model Instances
1809
+ c.filter(:item=>o2).all.should == [o]
1810
+ c.filter(:items=>o).all.should == [o2]
1811
+ c.filter(:related_items=>o2).all.should == [o]
1812
+ c.filter(:other_item=>o).all.should == [o2]
1813
+ c.filter(:other_items=>o2).all.should == [o]
1814
+ c.filter(:other_related_items=>o).all.should == [o]
1815
+ c.filter(:mtm_items=>o).all.should == [o]
1816
+
1817
+ # Filter By Associations - Model Datasets
1818
+ c.filter(:item=>c.filter(:id=>o2.id)).all.should == [o]
1819
+ c.filter(:items=>c.filter(:id=>o.id)).all.should == [o2]
1820
+ c.filter(:related_items=>c.filter(:id=>o2.id)).all.should == [o]
1821
+ c.filter(:other_item=>c.filter(:id=>o.id)).all.should == [o2]
1822
+ c.filter(:other_items=>c.filter(:id=>o2.id)).all.should == [o]
1823
+ c.filter(:other_related_items=>c.filter(:id=>o.id)).all.should == [o]
1824
+ c.filter(:mtm_items=>c.filter(:id=>o.id)).all.should == [o]
1597
1825
  end
1598
1826
 
1599
1827
  specify 'operations/functions with pg_hstore_ops' do
1600
- Sequel.extension :pg_hstore_ops
1601
- Sequel.extension :pg_array
1602
- Sequel.extension :pg_array_ops
1828
+ Sequel.extension :pg_hstore_ops, :pg_array, :pg_array_ops
1603
1829
  @db.create_table!(:items){hstore :h1; hstore :h2; hstore :h3; String :t}
1604
1830
  @ds.insert({'a'=>'b', 'c'=>nil}.hstore, {'a'=>'b'}.hstore, {'d'=>'e'}.hstore)
1605
1831
  h1 = :h1.hstore
@@ -1684,9 +1910,8 @@ end if POSTGRES_DB.type_supported?(:hstore)
1684
1910
 
1685
1911
  describe 'PostgreSQL json type' do
1686
1912
  before(:all) do
1687
- Sequel.extension :pg_json
1688
1913
  @db = POSTGRES_DB
1689
- @db.extend Sequel::Postgres::JSONDatabaseMethods
1914
+ @db.extension :pg_array, :pg_json
1690
1915
  @ds = @db[:items]
1691
1916
  @a = [1, 2, {'a'=>'b'}, 3.0]
1692
1917
  @h = {'a'=>'b', '1'=>[3, 4, 5]}
@@ -1728,6 +1953,24 @@ describe 'PostgreSQL json type' do
1728
1953
  end
1729
1954
  end
1730
1955
 
1956
+ specify 'insert and retrieve json[] values' do
1957
+ @db.create_table!(:items){column :j, 'json[]'}
1958
+ j = [{'a'=>1}.pg_json, ['b', 2].pg_json].pg_array
1959
+ @ds.insert(j)
1960
+ @ds.count.should == 1
1961
+ if @native
1962
+ rs = @ds.all
1963
+ v = rs.first[:j]
1964
+ v.should_not be_a_kind_of(Array)
1965
+ v.to_a.should be_a_kind_of(Array)
1966
+ v.should == j
1967
+ v.to_a.should == j
1968
+ @ds.delete
1969
+ @ds.insert(rs.first)
1970
+ @ds.all.should == rs
1971
+ end
1972
+ end
1973
+
1731
1974
  specify 'use json in bound variables' do
1732
1975
  @db.create_table!(:items){json :i}
1733
1976
  @ds.call(:insert, {:i=>@h.pg_json}, {:i=>:$i})
@@ -1740,6 +1983,13 @@ describe 'PostgreSQL json type' do
1740
1983
  @ds.get(:i).should == @a
1741
1984
  @ds.filter(:i.cast(String)=>:$i).call(:first, :i=>@a.pg_json).should == {:i=>@a}
1742
1985
  @ds.filter(:i.cast(String)=>:$i).call(:first, :i=>[].pg_json).should == nil
1986
+
1987
+ @db.create_table!(:items){column :i, 'json[]'}
1988
+ j = [{'a'=>1}.pg_json, ['b', 2].pg_json].pg_array(:text)
1989
+ @ds.call(:insert, {:i=>j}, {:i=>:$i})
1990
+ @ds.get(:i).should == j
1991
+ @ds.filter(:i.cast('text[]')=>:$i).call(:first, :i=>j).should == {:i=>j}
1992
+ @ds.filter(:i.cast('text[]')=>:$i).call(:first, :i=>[].pg_array).should == nil
1743
1993
  end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
1744
1994
 
1745
1995
  specify 'with models' do
@@ -1758,9 +2008,8 @@ describe 'PostgreSQL inet/cidr types' do
1758
2008
  ipv6_broken = (IPAddr.new('::1'); false) rescue true
1759
2009
 
1760
2010
  before(:all) do
1761
- Sequel.extension :pg_inet
1762
2011
  @db = POSTGRES_DB
1763
- @db.extend Sequel::Postgres::InetDatabaseMethods
2012
+ @db.extension :pg_array, :pg_inet
1764
2013
  @ds = @db[:items]
1765
2014
  @v4 = '127.0.0.1'
1766
2015
  @v4nm = '127.0.0.0/8'
@@ -1811,6 +2060,24 @@ describe 'PostgreSQL inet/cidr types' do
1811
2060
  end
1812
2061
  end
1813
2062
 
2063
+ specify 'insert and retrieve inet/cidr/macaddr array values' do
2064
+ @db.create_table!(:items){column :i, 'inet[]'; column :c, 'cidr[]'; column :m, 'macaddr[]'}
2065
+ @ds.insert([@ipv4].pg_array('inet'), [@ipv4nm].pg_array('cidr'), ['12:34:56:78:90:ab'].pg_array('macaddr'))
2066
+ @ds.count.should == 1
2067
+ if @native
2068
+ rs = @ds.all
2069
+ rs.first.values.all?{|c| c.is_a?(Sequel::Postgres::PGArray)}.should be_true
2070
+ rs.first[:i].first.should == @ipv4
2071
+ rs.first[:c].first.should == @ipv4nm
2072
+ rs.first[:m].first.should == '12:34:56:78:90:ab'
2073
+ rs.first[:i].first.should be_a_kind_of(IPAddr)
2074
+ rs.first[:c].first.should be_a_kind_of(IPAddr)
2075
+ @ds.delete
2076
+ @ds.insert(rs.first)
2077
+ @ds.all.should == rs
2078
+ end
2079
+ end
2080
+
1814
2081
  specify 'use ipaddr in bound variables' do
1815
2082
  @db.create_table!(:items){inet :i; cidr :c}
1816
2083
 
@@ -1829,6 +2096,12 @@ describe 'PostgreSQL inet/cidr types' do
1829
2096
  @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == nil
1830
2097
  @ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv6, :c=>@ipv6nm).should == 1
1831
2098
  end
2099
+
2100
+ @db.create_table!(:items){column :i, 'inet[]'; column :c, 'cidr[]'; column :m, 'macaddr[]'}
2101
+ @ds.call(:insert, {:i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']}, {:i=>:$i, :c=>:$c, :m=>:$m})
2102
+ @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:first, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).should == {:i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']}
2103
+ @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:first, :i=>[], :c=>[], :m=>[]).should == nil
2104
+ @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:delete, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).should == 1
1832
2105
  end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
1833
2106
 
1834
2107
  specify 'with models' do
@@ -1844,4 +2117,275 @@ describe 'PostgreSQL inet/cidr types' do
1844
2117
  c.create(:i=>@ipv6, :c=>@ipv6nm).values.values_at(:i, :c).should == [@ipv6, @ipv6nm]
1845
2118
  end
1846
2119
  end
2120
+ end
2121
+
2122
+ describe 'PostgreSQL range types' do
2123
+ before(:all) do
2124
+ @db = POSTGRES_DB
2125
+ @db.extension :pg_array, :pg_range
2126
+ @ds = @db[:items]
2127
+ @map = {:i4=>'int4range', :i8=>'int8range', :n=>'numrange', :d=>'daterange', :t=>'tsrange', :tz=>'tstzrange'}
2128
+ @r = {:i4=>1...2, :i8=>2...3, :n=>BigDecimal.new('1.0')..BigDecimal.new('2.0'), :d=>Date.today...(Date.today+1), :t=>Time.local(2011, 1)..Time.local(2011, 2), :tz=>Time.local(2011, 1)..Time.local(2011, 2)}
2129
+ @ra = {}
2130
+ @pgr = {}
2131
+ @pgra = {}
2132
+ @r.each{|k, v| @ra[k] = [v].pg_array(@map[k])}
2133
+ @r.each{|k, v| @pgr[k] = v.pg_range}
2134
+ @r.each{|k, v| @pgra[k] = [v.pg_range].pg_array(@map[k])}
2135
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2136
+ end
2137
+ after do
2138
+ @db.drop_table?(:items)
2139
+ end
2140
+
2141
+ specify 'insert and retrieve range type values' do
2142
+ @db.create_table!(:items){int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
2143
+ [@r, @pgr].each do |input|
2144
+ h = {}
2145
+ input.each{|k, v| h[k] = Sequel.cast(v, @map[k])}
2146
+ @ds.insert(h)
2147
+ @ds.count.should == 1
2148
+ if @native
2149
+ rs = @ds.all
2150
+ rs.first.each do |k, v|
2151
+ v.should_not be_a_kind_of(Range)
2152
+ v.to_range.should be_a_kind_of(Range)
2153
+ v.should == @r[k]
2154
+ v.to_range.should == @r[k]
2155
+ end
2156
+ @ds.delete
2157
+ @ds.insert(rs.first)
2158
+ @ds.all.should == rs
2159
+ end
2160
+ @ds.delete
2161
+ end
2162
+ end
2163
+
2164
+ specify 'insert and retrieve arrays of range type values' do
2165
+ @db.create_table!(:items){column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
2166
+ [@ra, @pgra].each do |input|
2167
+ @ds.insert(input)
2168
+ @ds.count.should == 1
2169
+ if @native
2170
+ rs = @ds.all
2171
+ rs.first.each do |k, v|
2172
+ v.should_not be_a_kind_of(Array)
2173
+ v.to_a.should be_a_kind_of(Array)
2174
+ v.first.should_not be_a_kind_of(Range)
2175
+ v.first.to_range.should be_a_kind_of(Range)
2176
+ v.should == @ra[k].to_a
2177
+ v.first.should == @r[k]
2178
+ end
2179
+ @ds.delete
2180
+ @ds.insert(rs.first)
2181
+ @ds.all.should == rs
2182
+ end
2183
+ @ds.delete
2184
+ end
2185
+ end
2186
+
2187
+ specify 'use range types in bound variables' do
2188
+ @db.create_table!(:items){int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
2189
+ h = {}
2190
+ @r.keys.each{|k| h[k] = :"$#{k}"}
2191
+ r2 = {}
2192
+ @r.each{|k, v| r2[k] = Range.new(v.begin, v.end+2)}
2193
+ @ds.call(:insert, @r, h)
2194
+ @ds.first.should == @r
2195
+ @ds.filter(h).call(:first, @r).should == @r
2196
+ @ds.filter(h).call(:first, @pgr).should == @r
2197
+ @ds.filter(h).call(:first, r2).should == nil
2198
+ @ds.filter(h).call(:delete, @r).should == 1
2199
+
2200
+ @db.create_table!(:items){column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
2201
+ @r.each{|k, v| r2[k] = [Range.new(v.begin, v.end+2)]}
2202
+ @ds.call(:insert, @ra, h)
2203
+ @ds.filter(h).call(:first, @ra).each{|k, v| v.should == @ra[k].to_a}
2204
+ @ds.filter(h).call(:first, @pgra).each{|k, v| v.should == @ra[k].to_a}
2205
+ @ds.filter(h).call(:first, r2).should == nil
2206
+ @ds.filter(h).call(:delete, @ra).should == 1
2207
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2208
+
2209
+ specify 'with models' do
2210
+ @db.create_table!(:items){primary_key :id; int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
2211
+ c = Class.new(Sequel::Model(@db[:items]))
2212
+ c.plugin :typecast_on_load, :i4, :i8, :n, :d, :t, :tz unless @native
2213
+ v = c.create(@r).values
2214
+ v.delete(:id)
2215
+ v.should == @r
2216
+
2217
+ unless @db.adapter_scheme == :jdbc
2218
+ @db.create_table!(:items){primary_key :id; column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
2219
+ c = Class.new(Sequel::Model(@db[:items]))
2220
+ c.plugin :typecast_on_load, :i4, :i8, :n, :d, :t, :tz unless @native
2221
+ v = c.create(@ra).values
2222
+ v.delete(:id)
2223
+ v.each{|k,v| v.should == @ra[k].to_a}
2224
+ end
2225
+ end
2226
+
2227
+ specify 'operations/functions with pg_range_ops' do
2228
+ Sequel.extension :pg_range_ops
2229
+
2230
+ @db.get((1..5).pg_range(:int4range).op.contains(2..4)).should be_true
2231
+ @db.get((1..5).pg_range(:int4range).op.contains(3..6)).should be_false
2232
+ @db.get((1..5).pg_range(:int4range).op.contains(0..6)).should be_false
2233
+
2234
+ @db.get((1..5).pg_range(:int4range).op.contained_by(0..6)).should be_true
2235
+ @db.get((1..5).pg_range(:int4range).op.contained_by(3..6)).should be_false
2236
+ @db.get((1..5).pg_range(:int4range).op.contained_by(2..4)).should be_false
2237
+
2238
+ @db.get((1..5).pg_range(:int4range).op.overlaps(5..6)).should be_true
2239
+ @db.get((1...5).pg_range(:int4range).op.overlaps(5..6)).should be_false
2240
+
2241
+ @db.get((1..5).pg_range(:int4range).op.left_of(6..10)).should be_true
2242
+ @db.get((1..5).pg_range(:int4range).op.left_of(5..10)).should be_false
2243
+ @db.get((1..5).pg_range(:int4range).op.left_of(-1..0)).should be_false
2244
+ @db.get((1..5).pg_range(:int4range).op.left_of(-1..3)).should be_false
2245
+
2246
+ @db.get((1..5).pg_range(:int4range).op.right_of(6..10)).should be_false
2247
+ @db.get((1..5).pg_range(:int4range).op.right_of(5..10)).should be_false
2248
+ @db.get((1..5).pg_range(:int4range).op.right_of(-1..0)).should be_true
2249
+ @db.get((1..5).pg_range(:int4range).op.right_of(-1..3)).should be_false
2250
+
2251
+ @db.get((1..5).pg_range(:int4range).op.starts_before(6..10)).should be_true
2252
+ @db.get((1..5).pg_range(:int4range).op.starts_before(5..10)).should be_true
2253
+ @db.get((1..5).pg_range(:int4range).op.starts_before(-1..0)).should be_false
2254
+ @db.get((1..5).pg_range(:int4range).op.starts_before(-1..3)).should be_false
2255
+
2256
+ @db.get((1..5).pg_range(:int4range).op.ends_after(6..10)).should be_false
2257
+ @db.get((1..5).pg_range(:int4range).op.ends_after(5..10)).should be_false
2258
+ @db.get((1..5).pg_range(:int4range).op.ends_after(-1..0)).should be_true
2259
+ @db.get((1..5).pg_range(:int4range).op.ends_after(-1..3)).should be_true
2260
+
2261
+ @db.get((1..5).pg_range(:int4range).op.adjacent_to(6..10)).should be_true
2262
+ @db.get((1...5).pg_range(:int4range).op.adjacent_to(6..10)).should be_false
2263
+
2264
+ @db.get(((1..5).pg_range(:int4range).op + (6..10)).adjacent_to(6..10)).should be_false
2265
+ @db.get(((1..5).pg_range(:int4range).op + (6..10)).adjacent_to(11..20)).should be_true
2266
+
2267
+ @db.get(((1..5).pg_range(:int4range).op * (2..6)).adjacent_to(6..10)).should be_true
2268
+ @db.get(((1..4).pg_range(:int4range).op * (2..6)).adjacent_to(6..10)).should be_false
2269
+
2270
+ @db.get(((1..5).pg_range(:int4range).op - (2..6)).adjacent_to(2..10)).should be_true
2271
+ @db.get(((0..4).pg_range(:int4range).op - (3..6)).adjacent_to(4..10)).should be_false
2272
+
2273
+ @db.get((0..4).pg_range(:int4range).op.lower).should == 0
2274
+ @db.get((0..4).pg_range(:int4range).op.upper).should == 5
2275
+
2276
+ @db.get((0..4).pg_range(:int4range).op.isempty).should be_false
2277
+ @db.get(Sequel::Postgres::PGRange.empty(:int4range).op.isempty).should be_true
2278
+
2279
+ @db.get((1..5).pg_range(:numrange).op.lower_inc).should be_true
2280
+ @db.get(Sequel::Postgres::PGRange.new(1, 5, :exclude_begin=>true, :db_type=>:numrange).op.lower_inc).should be_false
2281
+
2282
+ @db.get((1..5).pg_range(:numrange).op.upper_inc).should be_true
2283
+ @db.get((1...5).pg_range(:numrange).op.upper_inc).should be_false
2284
+
2285
+ @db.get(Sequel::Postgres::PGRange.new(1, 5, :db_type=>:int4range).op.lower_inf).should be_false
2286
+ @db.get(Sequel::Postgres::PGRange.new(nil, 5, :db_type=>:int4range).op.lower_inf).should be_true
2287
+
2288
+ @db.get(Sequel::Postgres::PGRange.new(1, 5, :db_type=>:int4range).op.upper_inf).should be_false
2289
+ @db.get(Sequel::Postgres::PGRange.new(1, nil, :db_type=>:int4range).op.upper_inf).should be_true
2290
+ end
1847
2291
  end if POSTGRES_DB.server_version >= 90200
2292
+
2293
+ describe 'PostgreSQL interval types' do
2294
+ before(:all) do
2295
+ @db = POSTGRES_DB
2296
+ @db.extension :pg_array, :pg_interval
2297
+ @ds = @db[:items]
2298
+ @native = POSTGRES_DB.adapter_scheme == :postgres
2299
+ end
2300
+ after(:all) do
2301
+ Sequel::Postgres::PG_TYPES.delete(1186)
2302
+ end
2303
+ after do
2304
+ @db.drop_table?(:items)
2305
+ end
2306
+
2307
+ specify 'insert and retrieve interval values' do
2308
+ @db.create_table!(:items){interval :i}
2309
+ [
2310
+ ['0', '00:00:00', 0, [[:seconds, 0]]],
2311
+ ['1 microsecond', '00:00:00.000001', 0.000001, [[:seconds, 0.000001]]],
2312
+ ['1 millisecond', '00:00:00.001', 0.001, [[:seconds, 0.001]]],
2313
+ ['1 second', '00:00:01', 1, [[:seconds, 1]]],
2314
+ ['1 minute', '00:01:00', 60, [[:seconds, 60]]],
2315
+ ['1 hour', '01:00:00', 3600, [[:seconds, 3600]]],
2316
+ ['1 day', '1 day', 86400, [[:days, 1]]],
2317
+ ['1 week', '7 days', 86400*7, [[:days, 7]]],
2318
+ ['1 month', '1 mon', 86400*30, [[:months, 1]]],
2319
+ ['1 year', '1 year', 31557600, [[:years, 1]]],
2320
+ ['1 decade', '10 years', 31557600*10, [[:years, 10]]],
2321
+ ['1 century', '100 years', 31557600*100, [[:years, 100]]],
2322
+ ['1 millennium', '1000 years', 31557600*1000, [[:years, 1000]]],
2323
+ ['1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds', '1 year 2 mons 25 days 05:06:07', 31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]]],
2324
+ ['-1 year +2 months -3 weeks +4 days -5 hours +6 minutes -7 seconds', '-10 mons -17 days -04:54:07', -10*86400*30 - 3*86400*7 + 4*86400 - 5*3600 + 6*60 - 7, [[:months, -10], [:days, -17], [:seconds, -17647]]],
2325
+ ['+2 years -1 months +3 weeks -4 days +5 hours -6 minutes +7 seconds', '1 year 11 mons 17 days 04:54:07', 31557600 + 11*86400*30 + 3*86400*7 - 4*86400 + 5*3600 - 6*60 + 7, [[:years, 1], [:months, 11], [:days, 17], [:seconds, 17647]]],
2326
+ ].each do |instr, outstr, value, parts|
2327
+ @ds.insert(instr)
2328
+ @ds.count.should == 1
2329
+ if @native
2330
+ @ds.get(Sequel.cast(:i, String)).should == outstr
2331
+ rs = @ds.all
2332
+ rs.first[:i].is_a?(ActiveSupport::Duration).should be_true
2333
+ rs.first[:i].should == ActiveSupport::Duration.new(value, parts)
2334
+ rs.first[:i].parts.sort_by{|k,v| k.to_s}.should == parts.sort_by{|k,v| k.to_s}
2335
+ @ds.delete
2336
+ @ds.insert(rs.first)
2337
+ @ds.all.should == rs
2338
+ end
2339
+ @ds.delete
2340
+ end
2341
+ end
2342
+
2343
+ specify 'insert and retrieve interval array values' do
2344
+ @db.create_table!(:items){column :i, 'interval[]'}
2345
+ @ds.insert(['1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds'].pg_array('interval'))
2346
+ @ds.count.should == 1
2347
+ if @native
2348
+ rs = @ds.all
2349
+ rs.first[:i].is_a?(Sequel::Postgres::PGArray).should be_true
2350
+ rs.first[:i].first.is_a?(ActiveSupport::Duration).should be_true
2351
+ rs.first[:i].first.should == ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
2352
+ rs.first[:i].first.parts.sort_by{|k,v| k.to_s}.should == [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]].sort_by{|k,v| k.to_s}
2353
+ @ds.delete
2354
+ @ds.insert(rs.first)
2355
+ @ds.all.should == rs
2356
+ end
2357
+ end
2358
+
2359
+ specify 'use intervals in bound variables' do
2360
+ @db.create_table!(:items){interval :i}
2361
+ @ds.insert('1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds')
2362
+ d = @ds.get(:i)
2363
+ @ds.delete
2364
+
2365
+ @ds.call(:insert, {:i=>d}, {:i=>:$i})
2366
+ @ds.get(:i).should == d
2367
+ @ds.filter(:i=>:$i).call(:first, :i=>d).should == {:i=>d}
2368
+ @ds.filter(:i=>:$i).call(:first, :i=>'0').should == nil
2369
+ @ds.filter(:i=>:$i).call(:delete, :i=>d).should == 1
2370
+
2371
+ @db.create_table!(:items){column :i, 'interval[]'}
2372
+ @ds.call(:insert, {:i=>[d]}, {:i=>:$i})
2373
+ @ds.filter(:i=>:$i).call(:first, :i=>[d]).should == {:i=>[d]}
2374
+ @ds.filter(:i=>:$i).call(:first, :i=>[]).should == nil
2375
+ @ds.filter(:i=>:$i).call(:delete, :i=>[d]).should == 1
2376
+ end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2377
+
2378
+ specify 'with models' do
2379
+ @db.create_table!(:items) do
2380
+ primary_key :id
2381
+ interval :i
2382
+ end
2383
+ c = Class.new(Sequel::Model(@db[:items]))
2384
+ c.plugin :typecast_on_load, :i, :c unless @native
2385
+ v = c.create(:i=>'1 year 2 mons 25 days 05:06:07').i
2386
+ v.is_a?(ActiveSupport::Duration).should be_true
2387
+ v.should == ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
2388
+ v.parts.sort_by{|k,v| k.to_s}.should == [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]].sort_by{|k,v| k.to_s}
2389
+ end
2390
+ end if ((require 'active_support/duration'; require 'active_support/inflector'; require 'active_support/core_ext/string/inflections'; true) rescue false)
2391
+