sequel 4.8.0 → 4.9.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +4 -0
  5. data/doc/postgresql.rdoc +27 -3
  6. data/doc/release_notes/4.9.0.txt +190 -0
  7. data/doc/security.rdoc +1 -1
  8. data/doc/testing.rdoc +2 -2
  9. data/doc/validations.rdoc +8 -0
  10. data/lib/sequel/adapters/jdbc.rb +5 -3
  11. data/lib/sequel/adapters/jdbc/derby.rb +2 -8
  12. data/lib/sequel/adapters/jdbc/h2.rb +2 -13
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
  14. data/lib/sequel/adapters/mysql2.rb +11 -1
  15. data/lib/sequel/adapters/postgres.rb +33 -10
  16. data/lib/sequel/adapters/shared/db2.rb +2 -10
  17. data/lib/sequel/adapters/shared/mssql.rb +10 -8
  18. data/lib/sequel/adapters/shared/oracle.rb +9 -24
  19. data/lib/sequel/adapters/shared/postgres.rb +32 -9
  20. data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
  21. data/lib/sequel/adapters/shared/sqlite.rb +4 -7
  22. data/lib/sequel/database/schema_methods.rb +15 -0
  23. data/lib/sequel/dataset.rb +1 -1
  24. data/lib/sequel/dataset/actions.rb +159 -27
  25. data/lib/sequel/dataset/graph.rb +29 -7
  26. data/lib/sequel/dataset/misc.rb +6 -0
  27. data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
  28. data/lib/sequel/dataset/query.rb +2 -0
  29. data/lib/sequel/dataset/sql.rb +103 -91
  30. data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
  31. data/lib/sequel/extensions/pg_array.rb +68 -106
  32. data/lib/sequel/extensions/pg_hstore.rb +5 -5
  33. data/lib/sequel/extensions/schema_dumper.rb +49 -49
  34. data/lib/sequel/model.rb +4 -2
  35. data/lib/sequel/model/associations.rb +1 -1
  36. data/lib/sequel/model/base.rb +136 -3
  37. data/lib/sequel/model/errors.rb +6 -0
  38. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  39. data/lib/sequel/plugins/eager_each.rb +9 -0
  40. data/lib/sequel/plugins/nested_attributes.rb +2 -2
  41. data/lib/sequel/plugins/timestamps.rb +2 -2
  42. data/lib/sequel/plugins/touch.rb +2 -2
  43. data/lib/sequel/sql.rb +20 -15
  44. data/lib/sequel/version.rb +1 -1
  45. data/spec/adapters/postgres_spec.rb +70 -8
  46. data/spec/core/dataset_spec.rb +172 -27
  47. data/spec/core/expression_filters_spec.rb +3 -3
  48. data/spec/core/object_graph_spec.rb +17 -1
  49. data/spec/core/placeholder_literalizer_spec.rb +128 -0
  50. data/spec/core/schema_spec.rb +54 -0
  51. data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
  52. data/spec/extensions/defaults_setter_spec.rb +12 -0
  53. data/spec/extensions/eager_each_spec.rb +6 -0
  54. data/spec/extensions/nested_attributes_spec.rb +14 -2
  55. data/spec/extensions/pg_array_spec.rb +15 -7
  56. data/spec/extensions/shared_caching_spec.rb +5 -5
  57. data/spec/extensions/timestamps_spec.rb +9 -0
  58. data/spec/extensions/touch_spec.rb +9 -0
  59. data/spec/integration/database_test.rb +1 -1
  60. data/spec/integration/dataset_test.rb +27 -5
  61. data/spec/model/eager_loading_spec.rb +32 -0
  62. data/spec/model/model_spec.rb +119 -9
  63. metadata +8 -2
data/lib/sequel/model.rb CHANGED
@@ -80,7 +80,7 @@ module Sequel
80
80
 
81
81
  # Class methods added to model that call the method of the same name on the dataset
82
82
  DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
83
- [:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases]
83
+ [:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
84
84
 
85
85
  # Boolean settings that can be modified at the global, class, or instance level.
86
86
  BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
@@ -119,7 +119,7 @@ module Sequel
119
119
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
120
120
  :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
121
121
  :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
122
- :@fast_instance_delete_sql=>nil,
122
+ :@fast_instance_delete_sql=>nil, :@finders=>:dup, :@finder_loaders=>:dup,
123
123
  :@db=>nil, :@default_set_fields_options=>:dup}
124
124
 
125
125
  # Regular expression that determines if a method name is normal in the sense that
@@ -138,6 +138,8 @@ module Sequel
138
138
  @dataset_method_modules = []
139
139
  @default_eager_limit_strategy = true
140
140
  @default_set_fields_options = {}
141
+ @finders = {}
142
+ @finder_loaders = {}
141
143
  @overridable_methods_module = nil
142
144
  @fast_pk_lookup_sql = nil
143
145
  @fast_instance_delete_sql = nil
@@ -2324,7 +2324,7 @@ END
2324
2324
  orig_ds = ds
2325
2325
  local_opts = ds.opts[:eager_graph][:local]
2326
2326
  limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
2327
- ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback, :join_type=>local_opts[:join_type], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
2327
+ ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>local_opts[:join_type], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
2328
2328
  if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
2329
2329
  ds = ds.order_more(*qualified_expression(order, assoc_table_alias))
2330
2330
  end
@@ -107,7 +107,7 @@ module Sequel
107
107
  # # => #<Artist {:name=>'Bob', ...}>
108
108
  def [](*args)
109
109
  args = args.first if args.size <= 1
110
- args.is_a?(Hash) ? dataset[args] : (primary_key_lookup(args) unless args.nil?)
110
+ args.is_a?(Hash) ? first_where(args) : (primary_key_lookup(args) unless args.nil?)
111
111
  end
112
112
 
113
113
  # Initializes a model instance as an existing record. This constructor is
@@ -308,7 +308,12 @@ module Sequel
308
308
  # Artist.find{name > 'M'}
309
309
  # # SELECT * FROM artists WHERE (name > 'M') LIMIT 1
310
310
  def find(*args, &block)
311
- filter(*args, &block).first
311
+ if args.length == 1 && !block
312
+ # Use optimized finder
313
+ first_where(args.first)
314
+ else
315
+ filter(*args, &block).first
316
+ end
312
317
  end
313
318
 
314
319
  # Like +find+ but invokes create with given conditions when record does not
@@ -327,6 +332,114 @@ module Sequel
327
332
  find(cond) || create(cond, &block)
328
333
  end
329
334
 
335
+
336
+ FINDER_TYPES = [:first, :all, :each, :get].freeze
337
+
338
+ # Create an optimized finder method using a dataset placeholder literalizer.
339
+ # This pre-computes the SQL to use for the query, except for given arguments.
340
+ #
341
+ # There are two ways to use this. The recommended way is to pass a symbol
342
+ # that represents a model class method that returns a dataset:
343
+ #
344
+ # def Artist.by_name(name)
345
+ # where(:name=>name)
346
+ # end
347
+ #
348
+ # Artist.finder :by_name
349
+ #
350
+ # This creates an optimized first_by_name method, which you can call normally:
351
+ #
352
+ # Artist.first_by_name("Joe")
353
+ #
354
+ # The alternative way to use this to pass your own block:
355
+ #
356
+ # Artist.finder(:name=>:first_by_name){|pl, ds| ds.where(:name=>pl.arg).limit(1)}
357
+ #
358
+ # Note that if you pass your own block, you are responsible for manually setting
359
+ # limits if necessary (as shown above).
360
+ #
361
+ # Options:
362
+ # :arity :: When using a symbol method name, this specifies the arity of the method.
363
+ # This should be used if if the method accepts an arbitrary number of arguments,
364
+ # or the method has default argument values. Note that if the method is defined
365
+ # as a dataset method, the class method Sequel creates accepts an arbitrary number
366
+ # of arguments, so you should use this option in that case. If you want to handle
367
+ # multiple possible arities, you need to call the finder method multiple times with
368
+ # unique :arity and :name methods each time.
369
+ # :name :: The name of the method to create. This must be given if you pass a block.
370
+ # If you use a symbol, this defaults to the symbol prefixed by the type.
371
+ # :mod :: The module in which to create the finder method. Defaults to the singleton
372
+ # class of the model.
373
+ # :type :: The type of query to run. Can be :first, :each, :all, or :get, defaults to
374
+ # :first.
375
+ #
376
+ # Caveats:
377
+ #
378
+ # This doesn't handle all possible cases. For example, if you have a method such as:
379
+ #
380
+ # def Artist.by_name(name)
381
+ # name ? where(:name=>name) : exclude(:name=>nil)
382
+ # end
383
+ #
384
+ # Then calling a finder without an argument will not work as you expect.
385
+ #
386
+ # Artist.finder :by_name
387
+ # Artist.by_name(nil).first
388
+ # # WHERE (name IS NOT NULL)
389
+ # Artist.first_by_name(nil)
390
+ # # WHERE (name IS NULL)
391
+ #
392
+ # See Dataset::PlaceholderLiteralizer for additional caveats.
393
+ def finder(meth=OPTS, opts=OPTS, &block)
394
+ if block
395
+ raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash)
396
+ raise Error, "cannot pass two option hashes to Model.finder" unless opts.equal?(OPTS)
397
+ opts = meth
398
+ raise Error, "must provide method name via :name option when passing block to Model.finder" unless meth_name = opts[:name]
399
+ end
400
+
401
+ type = opts.fetch(:type, :first)
402
+ raise Error, ":type option to Model.finder must be :first, :all, :each, or :get" unless FINDER_TYPES.include?(type)
403
+ limit1 = type == :first || type == :get
404
+ meth_name ||= opts[:name] || :"#{type}_#{meth}"
405
+
406
+ loader_proc = proc do |model|
407
+ unless block
408
+ argn = opts[:arity]
409
+ block = lambda do |pl, model2|
410
+ method = model2.method(meth)
411
+ argn ||= (method.arity < 0 ? method.arity.abs - 1 : method.arity)
412
+ args = (0...argn).map{pl.arg}
413
+ ds = method.call(*args)
414
+ ds = ds.limit(1) if limit1
415
+ ds
416
+ end
417
+ end
418
+
419
+ Sequel::Dataset::PlaceholderLiteralizer.loader(model, &block)
420
+ end
421
+ Sequel.synchronize{@finder_loaders[meth_name] = loader_proc}
422
+ mod = opts[:mod] || (class << self; self; end)
423
+ def_finder_method(mod, meth_name, type)
424
+ end
425
+
426
+ # An alias for calling first on the model's dataset, but with
427
+ # optimized handling of the single argument case.
428
+ def first(*args, &block)
429
+ if args.length == 1 && !block && !args.first.is_a?(Integer)
430
+ # Use optimized finder
431
+ first_where(args.first)
432
+ else
433
+ dataset.first(*args, &block)
434
+ end
435
+ end
436
+
437
+ # An alias for calling first! on the model's dataset, but with
438
+ # optimized handling of the single argument case.
439
+ def first!(*args, &block)
440
+ first(*args, &block) || raise(Sequel::NoMatchingRow)
441
+ end
442
+
330
443
  # Clear the setter_methods cache when a module is included, as it
331
444
  # may contain setter methods.
332
445
  def include(mod)
@@ -699,6 +812,24 @@ module Sequel
699
812
  end
700
813
  end
701
814
 
815
+ # Define a finder method in the given module with the given method name that
816
+ # load rows using the finder with the given name.
817
+ def def_finder_method(mod, meth, type)
818
+ mod.send(:define_method, meth){|*args, &block| finder_for(meth).send(type, *args, &block)}
819
+ end
820
+
821
+ # Find the finder to use for the give method. If a finder has not been loaded
822
+ # for the method, load the finder and set correctly in the finders hash, then
823
+ # return the finder.
824
+ def finder_for(meth)
825
+ unless finder = Sequel.synchronize{@finders[meth]}
826
+ finder_loader = @finder_loaders.fetch(meth)
827
+ finder = finder_loader.call(self)
828
+ Sequel.synchronize{@finders[meth] = finder}
829
+ end
830
+ finder
831
+ end
832
+
702
833
  # Get the schema from the database, fall back on checking the columns
703
834
  # via the database if that will return inaccurate results or if
704
835
  # it raises an error.
@@ -831,7 +962,7 @@ module Sequel
831
962
  ds.fetch_rows(sql){|r| return ds.row_proc.call(r)}
832
963
  nil
833
964
  else
834
- dataset[primary_key_hash(pk)]
965
+ first_where(primary_key_hash(pk))
835
966
  end
836
967
  end
837
968
 
@@ -849,6 +980,7 @@ module Sequel
849
980
  # Reset the instance dataset to a modified copy of the current dataset,
850
981
  # should be used whenever the model's dataset is modified.
851
982
  def reset_instance_dataset
983
+ @finders.clear if @finders
852
984
  @instance_dataset = @dataset.limit(1).naked if @dataset
853
985
  end
854
986
 
@@ -2055,5 +2187,6 @@ module Sequel
2055
2187
 
2056
2188
  extend ClassMethods
2057
2189
  plugin self
2190
+ finder(:where, :arity=>1, :mod=>ClassMethods)
2058
2191
  end
2059
2192
  end
@@ -29,6 +29,12 @@ module Sequel
29
29
  # errors.full_messages
30
30
  # # => ['name is not valid',
31
31
  # # 'hometown is not at least 2 letters']
32
+ #
33
+ # If the message is a Sequel::LiteralString, it will be used literally, without the column name:
34
+ #
35
+ # errors.add(:name, Sequel.lit("Album name is not valid"))
36
+ # errors.full_messages
37
+ # # => ['Album name is not valid']
32
38
  def full_messages
33
39
  inject([]) do |m, kv|
34
40
  att, errors = *kv
@@ -46,7 +46,7 @@ module Sequel
46
46
  when Sequel::CURRENT_DATE
47
47
  lambda{Date.today}
48
48
  when Sequel::CURRENT_TIMESTAMP
49
- lambda{Sequel.datetime_class.now}
49
+ lambda{dataset.current_datetime}
50
50
  else
51
51
  v
52
52
  end
@@ -22,6 +22,15 @@ module Sequel
22
22
  # Album.plugin :eager_each
23
23
  module EagerEach
24
24
  module DatasetMethods
25
+ # Don't call #all when attempting to load the columns.
26
+ def columns
27
+ if use_eager_all?
28
+ clone(:all_called=>true).columns
29
+ else
30
+ super
31
+ end
32
+ end
33
+
25
34
  # Call #all instead of #each if eager loading,
26
35
  # uless #each is being called by #all.
27
36
  def each(&block)
@@ -286,7 +286,7 @@ module Sequel
286
286
  def validate_associated_object(reflection, obj)
287
287
  return if reflection[:validate] == false
288
288
  association = reflection[:name]
289
- if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !obj.values[key]
289
+ if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !(pk_val = obj.values[key])
290
290
  # There could be a presence validation on the foreign key in the associated model,
291
291
  # which will fail if we validate before saving the current object. If there is
292
292
  # no value for the foreign key, set it to the current primary key value, or a dummy
@@ -295,7 +295,7 @@ module Sequel
295
295
  key = nil if pk
296
296
  end
297
297
  obj.errors.full_messages.each{|m| errors.add(association, m)} unless obj.valid?
298
- if key
298
+ if key && !pk_val
299
299
  # If we used a dummy value of 0, remove it so it doesn't accidently remain.
300
300
  obj.values.delete(key)
301
301
  end
@@ -79,7 +79,7 @@ module Sequel
79
79
  def set_create_timestamp(time=nil)
80
80
  field = model.create_timestamp_field
81
81
  meth = :"#{field}="
82
- self.send(meth, time||=Sequel.datetime_class.now) if respond_to?(field) && respond_to?(meth) && (model.create_timestamp_overwrite? || send(field).nil?)
82
+ self.send(meth, time||=model.dataset.current_datetime) if respond_to?(field) && respond_to?(meth) && (model.create_timestamp_overwrite? || send(field).nil?)
83
83
  set_update_timestamp(time) if model.set_update_timestamp_on_create?
84
84
  end
85
85
 
@@ -87,7 +87,7 @@ module Sequel
87
87
  # object has a setter method for the update timestamp field.
88
88
  def set_update_timestamp(time=nil)
89
89
  meth = :"#{model.update_timestamp_field}="
90
- self.send(meth, time||Sequel.datetime_class.now) if respond_to?(meth)
90
+ self.send(meth, time||model.dataset.current_datetime) if respond_to?(meth)
91
91
  end
92
92
  end
93
93
  end
@@ -130,9 +130,9 @@ module Sequel
130
130
  end
131
131
 
132
132
  # The value to use when modifying the touch column for the model instance.
133
- # Uses Time.now to work well with typecasting.
133
+ # Uses Time/DateTime.now to work well with typecasting.
134
134
  def touch_instance_value
135
- Time.now
135
+ model.dataset.current_datetime
136
136
  end
137
137
  end
138
138
  end
data/lib/sequel/sql.rb CHANGED
@@ -216,7 +216,7 @@ module Sequel
216
216
  case op
217
217
  when *N_ARITY_OPERATORS
218
218
  raise(Error, "The #{op} operator requires at least 1 argument") unless args.length >= 1
219
- old_args = args
219
+ old_args = args.map{|a| a.is_a?(self.class) && a.op == :NOOP ? a.args.first : a}
220
220
  args = []
221
221
  old_args.each{|a| a.is_a?(self.class) && a.op == op ? args.concat(a.args) : args.push(a)}
222
222
  when *TWO_ARITY_OPERATORS
@@ -798,20 +798,23 @@ module Sequel
798
798
  # arguments with the appropriate operator, and the & and | operators return
799
799
  # boolean expressions combining all of the arguments with either AND or OR.
800
800
  module OperatorBuilders
801
- %w'+ - * /'.each do |op|
802
- class_eval(<<-END, __FILE__, __LINE__ + 1)
803
- def #{op}(*args)
804
- SQL::NumericExpression.new(:#{op}, *args)
805
- end
806
- END
807
- end
808
-
809
- {'&'=>'AND', '|'=>'OR'}.each do |m, op|
810
- class_eval(<<-END, __FILE__, __LINE__ + 1)
811
- def #{m}(*args)
812
- SQL::BooleanExpression.new(:#{op}, *args)
813
- end
814
- END
801
+ {'::Sequel::SQL::NumericExpression'=>{'+'=>'+', '-'=>'-', '*'=>'*', '/'=>'/'},
802
+ '::Sequel::SQL::BooleanExpression'=>{'&'=>'AND', '|'=>'OR'}}.each do |klass, ops|
803
+ ops.each do |m, op|
804
+ class_eval(<<-END, __FILE__, __LINE__ + 1)
805
+ def #{m}(*args)
806
+ if (args.length == 1)
807
+ if (v = args.first).class.is_a?(#{klass})
808
+ v
809
+ else
810
+ #{klass}.new(:NOOP, v)
811
+ end
812
+ else
813
+ #{klass}.new(:#{op}, *args)
814
+ end
815
+ end
816
+ END
817
+ end
815
818
  end
816
819
 
817
820
  # Invert the given expression. Returns a <tt>Sequel::SQL::BooleanExpression</tt>
@@ -1012,6 +1015,8 @@ module Sequel
1012
1015
  StringExpression.like(l, r)
1013
1016
  when DelayedEvaluation
1014
1017
  Sequel.delay{from_value_pair(l, r.callable.call)}
1018
+ when Dataset::PlaceholderLiteralizer::Argument
1019
+ r.transform{|v| from_value_pair(l, v)}
1015
1020
  else
1016
1021
  new(:'=', l, r)
1017
1022
  end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 8
6
+ MINOR = 9
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -170,8 +170,31 @@ describe "A PostgreSQL database" do
170
170
  @db.server_version.should > 70000
171
171
  end
172
172
 
173
+ specify "should support disable_insert_returning" do
174
+ ds = @db[:public__testfk].disable_insert_returning
175
+ ds.delete
176
+ ds.insert.should == nil
177
+ id = ds.max(:id)
178
+ ds.select_order_map([:id, :i]).should == [[id, nil]]
179
+ ds.insert(:i=>id).should == nil
180
+ ds.select_order_map([:id, :i]).should == [[id, nil], [id+1, id]]
181
+ ds.insert_select(:i=>ds.max(:id)).should == nil
182
+ ds.select_order_map([:id, :i]).should == [[id, nil], [id+1, id]]
183
+ c = Class.new(Sequel::Model(ds))
184
+ c.class_eval do
185
+ def before_create
186
+ self.id = model.max(:id)+1
187
+ super
188
+ end
189
+ end
190
+ c.create(:i=>id+1).should == c.load(:id=>id+2, :i=>id+1)
191
+ ds.select_order_map([:id, :i]).should == [[id, nil], [id+1, id], [id+2, id+1]]
192
+ ds.delete
193
+ end
194
+
173
195
  specify "should support functions with and without quoting" do
174
196
  ds = @db[:public__testfk]
197
+ ds.delete
175
198
  ds.insert
176
199
  ds.get{sum(1)}.should == 1
177
200
  ds.get{Sequel.function('pg_catalog.sum', 1)}.should == 1
@@ -1497,6 +1520,23 @@ if DB.adapter_scheme == :postgres
1497
1520
  end
1498
1521
  end
1499
1522
 
1523
+ specify "should respect the :hold=>true option for creating the cursor WITH HOLD and not using a transaction" do
1524
+ @ds.use_cursor.each{@db.in_transaction?.should == true}
1525
+ check_sqls{@db.sqls.any?{|s| s =~ /WITH HOLD/}.should == false}
1526
+ @ds.use_cursor(:hold=>true).each{@db.in_transaction?.should == false}
1527
+ check_sqls{@db.sqls.any?{|s| s =~ /WITH HOLD/}.should == true}
1528
+ end
1529
+
1530
+ specify "should support updating individual rows based on a cursor" do
1531
+ @db.transaction(:rollback=>:always) do
1532
+ @ds.use_cursor(:rows_per_fetch=>1).each do |row|
1533
+ @ds.where_current_of.update(:x=>Sequel.*(row[:x], 10))
1534
+ end
1535
+ @ds.select_order_map(:x).should == (0..1000).map{|x| x * 10}
1536
+ end
1537
+ @ds.select_order_map(:x).should == (0..1000).to_a
1538
+ end
1539
+
1500
1540
  specify "should respect the :cursor_name option" do
1501
1541
  one_rows = []
1502
1542
  two_rows = []
@@ -1852,7 +1892,7 @@ describe 'PostgreSQL array handling' do
1852
1892
  column :r, 'real[]'
1853
1893
  column :dp, 'double precision[]'
1854
1894
  end
1855
- @tp.call.should == [:integer_array, :integer_array, :bigint_array, :float_array, :float_array]
1895
+ @tp.call.should == [:smallint_array, :integer_array, :bigint_array, :real_array, :float_array]
1856
1896
  @ds.insert(Sequel.pg_array([1], :int2), Sequel.pg_array([nil, 2], :int4), Sequel.pg_array([3, nil], :int8), Sequel.pg_array([4, nil, 4.5], :real), Sequel.pg_array([5, nil, 5.5], "double precision"))
1857
1897
  @ds.count.should == 1
1858
1898
  rs = @ds.all
@@ -1915,12 +1955,12 @@ describe 'PostgreSQL array handling' do
1915
1955
  column :vc, 'varchar[]'
1916
1956
  column :t, 'text[]'
1917
1957
  end
1918
- @tp.call.should == [:string_array, :string_array, :string_array]
1919
- @ds.insert(Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], 'char(4)'), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :varchar), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :text))
1958
+ @tp.call.should == [:character_array, :varchar_array, :string_array]
1959
+ @ds.insert(Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], 'char(4)'), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c', '', ''], :varchar), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :text))
1920
1960
  @ds.count.should == 1
1921
1961
  rs = @ds.all
1922
1962
  if @native
1923
- rs.should == [{:c=>['a ', nil, 'NULL', 'b"\'c'], :vc=>['a', nil, 'NULL', 'b"\'c'], :t=>['a', nil, 'NULL', 'b"\'c']}]
1963
+ rs.should == [{:c=>['a ', nil, 'NULL', 'b"\'c'], :vc=>['a', nil, 'NULL', 'b"\'c', '', ''], :t=>['a', nil, 'NULL', 'b"\'c']}]
1924
1964
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1925
1965
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1926
1966
  @ds.delete
@@ -1929,10 +1969,10 @@ describe 'PostgreSQL array handling' do
1929
1969
  end
1930
1970
 
1931
1971
  @ds.delete
1932
- @ds.insert(Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], 'char(4)'), Sequel.pg_array([[['a'], ['']], [['NULL'], ['b"\'c']]], :varchar), Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], :text))
1972
+ @ds.insert(Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], 'char(4)'), Sequel.pg_array([[['a[],\\[\\]\\,\\""NULL",'], ['']], [['NULL'], ['b"\'c']]], :varchar), Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], :text))
1933
1973
  rs = @ds.all
1934
1974
  if @native
1935
- rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
1975
+ rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a[],\\[\\]\\,\\""NULL",'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
1936
1976
  rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
1937
1977
  rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
1938
1978
  @ds.delete
@@ -1972,7 +2012,7 @@ describe 'PostgreSQL array handling' do
1972
2012
  column :tz, 'timetz[]'
1973
2013
  column :o, 'oid[]'
1974
2014
  end
1975
- @tp.call.should == [:blob_array, :time_timezone_array, :integer_array]
2015
+ @tp.call.should == [:blob_array, :time_timezone_array, :oid_array]
1976
2016
  @ds.insert(Sequel.pg_array([Sequel.blob("a\0"), nil], :bytea), Sequel.pg_array([t, nil], :timetz), Sequel.pg_array([1, 2, 3], :oid))
1977
2017
  @ds.count.should == 1
1978
2018
  if @native
@@ -2109,11 +2149,33 @@ describe 'PostgreSQL array handling' do
2109
2149
  end
2110
2150
  c = Class.new(Sequel::Model(@db[:items]))
2111
2151
  c.plugin :pg_typecast_on_load, :i, :f, :d, :t unless @native
2152
+ h = {:i=>[1,2, nil], :f=>[[1, 2.5], [3, 4.5]], :d=>[1, BigDecimal.new('1.000000000000000000001')], :t=>[%w'a b c', ['NULL', nil, '1']]}
2153
+ o = c.create(h)
2154
+ o.i.should == [1, 2, nil]
2155
+ o.f.should == [[1, 2.5], [3, 4.5]]
2156
+ o.d.should == [BigDecimal.new('1'), BigDecimal.new('1.000000000000000000001')]
2157
+ o.t.should == [%w'a b c', ['NULL', nil, '1']]
2158
+ c.where(:i=>o.i, :f=>o.f, :d=>o.d, :t=>o.t).all.should == [o]
2159
+ o2 = c.new(h)
2160
+ c.where(:i=>o2.i, :f=>o2.f, :d=>o2.d, :t=>o2.t).all.should == [o]
2161
+
2162
+ @db.create_table!(:items) do
2163
+ primary_key :id
2164
+ column :i, 'int2[]'
2165
+ column :f, 'real[]'
2166
+ column :d, 'numeric(30,28)[]'
2167
+ column :t, 'varchar[]'
2168
+ end
2169
+ c = Class.new(Sequel::Model(@db[:items]))
2170
+ c.plugin :pg_typecast_on_load, :i, :f, :d, :t unless @native
2112
2171
  o = c.create(:i=>[1,2, nil], :f=>[[1, 2.5], [3, 4.5]], :d=>[1, BigDecimal.new('1.000000000000000000001')], :t=>[%w'a b c', ['NULL', nil, '1']])
2113
2172
  o.i.should == [1, 2, nil]
2114
2173
  o.f.should == [[1, 2.5], [3, 4.5]]
2115
2174
  o.d.should == [BigDecimal.new('1'), BigDecimal.new('1.000000000000000000001')]
2116
2175
  o.t.should == [%w'a b c', ['NULL', nil, '1']]
2176
+ c.where(:i=>o.i, :f=>o.f, :d=>o.d, :t=>o.t).all.should == [o]
2177
+ o2 = c.new(h)
2178
+ c.where(:i=>o2.i, :f=>o2.f, :d=>o2.d, :t=>o2.t).all.should == [o]
2117
2179
  end
2118
2180
 
2119
2181
  specify 'operations/functions with pg_array_ops' do
@@ -2973,8 +3035,8 @@ end if (begin require 'active_support/duration'; require 'active_support/inflect
2973
3035
  describe 'PostgreSQL row-valued/composite types' do
2974
3036
  before(:all) do
2975
3037
  @db = DB
2976
- Sequel.extension :pg_array_ops, :pg_row_ops
2977
3038
  @db.extension :pg_array, :pg_row
3039
+ Sequel.extension :pg_array_ops, :pg_row_ops
2978
3040
  @ds = @db[:person]
2979
3041
 
2980
3042
  @db.create_table!(:address) do