sequel 3.36.1 → 3.37.0

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