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.
- 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
|