sequel 4.8.0 → 4.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/doc/association_basics.rdoc +1 -1
- data/doc/opening_databases.rdoc +4 -0
- data/doc/postgresql.rdoc +27 -3
- data/doc/release_notes/4.9.0.txt +190 -0
- data/doc/security.rdoc +1 -1
- data/doc/testing.rdoc +2 -2
- data/doc/validations.rdoc +8 -0
- data/lib/sequel/adapters/jdbc.rb +5 -3
- data/lib/sequel/adapters/jdbc/derby.rb +2 -8
- data/lib/sequel/adapters/jdbc/h2.rb +2 -13
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
- data/lib/sequel/adapters/mysql2.rb +11 -1
- data/lib/sequel/adapters/postgres.rb +33 -10
- data/lib/sequel/adapters/shared/db2.rb +2 -10
- data/lib/sequel/adapters/shared/mssql.rb +10 -8
- data/lib/sequel/adapters/shared/oracle.rb +9 -24
- data/lib/sequel/adapters/shared/postgres.rb +32 -9
- data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
- data/lib/sequel/adapters/shared/sqlite.rb +4 -7
- data/lib/sequel/database/schema_methods.rb +15 -0
- data/lib/sequel/dataset.rb +1 -1
- data/lib/sequel/dataset/actions.rb +159 -27
- data/lib/sequel/dataset/graph.rb +29 -7
- data/lib/sequel/dataset/misc.rb +6 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
- data/lib/sequel/dataset/query.rb +2 -0
- data/lib/sequel/dataset/sql.rb +103 -91
- data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
- data/lib/sequel/extensions/pg_array.rb +68 -106
- data/lib/sequel/extensions/pg_hstore.rb +5 -5
- data/lib/sequel/extensions/schema_dumper.rb +49 -49
- data/lib/sequel/model.rb +4 -2
- data/lib/sequel/model/associations.rb +1 -1
- data/lib/sequel/model/base.rb +136 -3
- data/lib/sequel/model/errors.rb +6 -0
- data/lib/sequel/plugins/defaults_setter.rb +1 -1
- data/lib/sequel/plugins/eager_each.rb +9 -0
- data/lib/sequel/plugins/nested_attributes.rb +2 -2
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/plugins/touch.rb +2 -2
- data/lib/sequel/sql.rb +20 -15
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +70 -8
- data/spec/core/dataset_spec.rb +172 -27
- data/spec/core/expression_filters_spec.rb +3 -3
- data/spec/core/object_graph_spec.rb +17 -1
- data/spec/core/placeholder_literalizer_spec.rb +128 -0
- data/spec/core/schema_spec.rb +54 -0
- data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
- data/spec/extensions/defaults_setter_spec.rb +12 -0
- data/spec/extensions/eager_each_spec.rb +6 -0
- data/spec/extensions/nested_attributes_spec.rb +14 -2
- data/spec/extensions/pg_array_spec.rb +15 -7
- data/spec/extensions/shared_caching_spec.rb +5 -5
- data/spec/extensions/timestamps_spec.rb +9 -0
- data/spec/extensions/touch_spec.rb +9 -0
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +27 -5
- data/spec/model/eager_loading_spec.rb +32 -0
- data/spec/model/model_spec.rb +119 -9
- 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
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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) ?
|
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
|
-
|
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
|
-
|
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
|
data/lib/sequel/model/errors.rb
CHANGED
@@ -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
|
@@ -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||=
|
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||
|
90
|
+
self.send(meth, time||model.dataset.current_datetime) if respond_to?(meth)
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
data/lib/sequel/plugins/touch.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
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
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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 == [:
|
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 == [:
|
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, :
|
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
|