sequel 4.8.0 → 4.9.0

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