sequel 3.35.0 → 3.36.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +78 -0
- data/Rakefile +3 -3
- data/bin/sequel +3 -1
- data/doc/advanced_associations.rdoc +154 -11
- data/doc/migration.rdoc +18 -0
- data/doc/object_model.rdoc +541 -0
- data/doc/opening_databases.rdoc +4 -1
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/schema_modification.rdoc +0 -6
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/jdbc.rb +11 -3
- data/lib/sequel/adapters/jdbc/mysql.rb +3 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +10 -8
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/mock.rb +2 -6
- data/lib/sequel/adapters/mysql.rb +3 -9
- data/lib/sequel/adapters/mysql2.rb +12 -11
- data/lib/sequel/adapters/postgres.rb +32 -40
- data/lib/sequel/adapters/shared/mssql.rb +15 -11
- data/lib/sequel/adapters/shared/mysql.rb +28 -3
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +59 -5
- data/lib/sequel/adapters/shared/sqlite.rb +3 -13
- data/lib/sequel/adapters/sqlite.rb +0 -7
- data/lib/sequel/adapters/swift/mysql.rb +2 -5
- data/lib/sequel/adapters/tinytds.rb +1 -2
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/connection_pool/threaded.rb +9 -1
- data/lib/sequel/database/dataset_defaults.rb +3 -1
- data/lib/sequel/database/misc.rb +7 -1
- data/lib/sequel/database/query.rb +11 -3
- data/lib/sequel/database/schema_generator.rb +40 -9
- data/lib/sequel/database/schema_methods.rb +6 -1
- data/lib/sequel/dataset/actions.rb +5 -5
- data/lib/sequel/dataset/prepared_statements.rb +3 -1
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/extensions/migration.rb +28 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -9
- data/lib/sequel/extensions/pg_inet.rb +89 -0
- data/lib/sequel/extensions/pg_json.rb +178 -0
- data/lib/sequel/extensions/schema_dumper.rb +24 -6
- data/lib/sequel/model/associations.rb +19 -15
- data/lib/sequel/model/base.rb +11 -12
- data/lib/sequel/plugins/composition.rb +1 -2
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/json_serializer.rb +41 -4
- data/lib/sequel/plugins/nested_attributes.rb +72 -52
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +271 -1
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/connection_pool_spec.rb +26 -1
- data/spec/core/database_spec.rb +19 -0
- data/spec/core/dataset_spec.rb +45 -5
- data/spec/core/expression_filters_spec.rb +31 -67
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/extensions/core_extensions_spec.rb +83 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/json_serializer_spec.rb +32 -1
- data/spec/extensions/migration_spec.rb +28 -0
- data/spec/extensions/nested_attributes_spec.rb +134 -1
- data/spec/extensions/optimistic_locking_spec.rb +15 -1
- data/spec/extensions/pg_hstore_spec.rb +1 -1
- data/spec/extensions/pg_inet_spec.rb +44 -0
- data/spec/extensions/pg_json_spec.rb +101 -0
- data/spec/extensions/prepared_statements_spec.rb +30 -0
- data/spec/extensions/rcte_tree_spec.rb +9 -0
- data/spec/extensions/schema_dumper_spec.rb +195 -7
- data/spec/extensions/serialization_spec.rb +4 -0
- data/spec/extensions/spec_helper.rb +9 -1
- data/spec/extensions/tactical_eager_loading_spec.rb +8 -0
- data/spec/integration/database_test.rb +5 -1
- data/spec/integration/prepared_statement_test.rb +20 -2
- data/spec/model/associations_spec.rb +27 -0
- data/spec/model/base_spec.rb +54 -0
- data/spec/model/model_spec.rb +6 -0
- data/spec/model/record_spec.rb +18 -0
- data/spec/rcov.opts +2 -0
- metadata +14 -3
@@ -87,20 +87,30 @@ module Sequel
|
|
87
87
|
# modified through the association_attributes= method to the specific fields given.
|
88
88
|
# * :limit - For *_to_many associations, a limit on the number of records
|
89
89
|
# that will be processed, to prevent denial of service attacks.
|
90
|
+
# * :reject_if - A proc that is given each attribute hash before it is
|
91
|
+
# passed to its associated object. If the proc returns a truthy
|
92
|
+
# value, the attribute hash is ignored.
|
90
93
|
# * :remove - Allow disassociation of nested records (can remove the associated
|
91
94
|
# object from the parent object, but not destroy the associated object).
|
92
|
-
# * :strict -
|
93
|
-
#
|
94
|
-
#
|
95
|
+
# * :strict - Kept for backward compatibility. Setting it to false is
|
96
|
+
# equivalent to setting :unmatched_pk to :ignore.
|
97
|
+
# * :transform - A proc to transform attribute hashes before they are
|
98
|
+
# passed to associated object. Takes two arguments, the parent object and
|
99
|
+
# the attribute hash. Uses the return value as the new attribute hash.
|
100
|
+
# * :unmatched_pk - Specify the action to be taken if a primary key is
|
101
|
+
# provided in a record, but it doesn't match an existing associated
|
102
|
+
# object. Set to :create to create a new object with that primary
|
103
|
+
# key, :ignore to ignore the record, or :raise to raise an error.
|
104
|
+
# The default is :raise.
|
95
105
|
#
|
96
|
-
# If a block is provided, it is
|
97
|
-
# the hash should be ignored, the block should return anything except false or nil.
|
106
|
+
# If a block is provided, it is used to set the :reject_if option.
|
98
107
|
def nested_attributes(*associations, &block)
|
99
108
|
include(self.nested_attributes_module ||= Module.new) unless nested_attributes_module
|
100
109
|
opts = associations.last.is_a?(Hash) ? associations.pop : {}
|
101
110
|
reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
|
102
111
|
reflections.each do |r|
|
103
112
|
r[:nested_attributes] = opts
|
113
|
+
r[:nested_attributes][:unmatched_pk] ||= opts.delete(:strict) == false ? :ignore : :raise
|
104
114
|
r[:nested_attributes][:reject_if] ||= block
|
105
115
|
def_nested_attribute_method(r)
|
106
116
|
end
|
@@ -133,7 +143,9 @@ module Sequel
|
|
133
143
|
def nested_attributes_check_key_modifications(reflection, obj)
|
134
144
|
keys = reflection.associated_object_keys.map{|x| obj.send(x)}
|
135
145
|
yield
|
136
|
-
|
146
|
+
unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
|
147
|
+
raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed")
|
148
|
+
end
|
137
149
|
end
|
138
150
|
|
139
151
|
# Create a new associated object with the given attributes, validate
|
@@ -148,6 +160,12 @@ module Sequel
|
|
148
160
|
after_save_hook{send(reflection.add_method, obj)}
|
149
161
|
else
|
150
162
|
associations[reflection[:name]] = obj
|
163
|
+
|
164
|
+
# Because we are modifying the associations cache manually before the
|
165
|
+
# setter is called, we still want to run the setter code even though
|
166
|
+
# the cached value will be the same as the given value.
|
167
|
+
@set_associated_object_if_same = true
|
168
|
+
|
151
169
|
# Don't need to validate the object twice if :validate association option is not false
|
152
170
|
# and don't want to validate it at all if it is false.
|
153
171
|
send(reflection[:type] == :many_to_one ? :before_save_hook : :after_save_hook){send(reflection.setter_method, obj.save(:validate=>false))}
|
@@ -155,16 +173,6 @@ module Sequel
|
|
155
173
|
obj
|
156
174
|
end
|
157
175
|
|
158
|
-
# Find an associated object with the matching pk. If a matching option
|
159
|
-
# is not found and the :strict option is not false, raise an Error.
|
160
|
-
def nested_attributes_find(reflection, pk)
|
161
|
-
pk = pk.to_s
|
162
|
-
unless obj = Array(send(reflection[:name])).find{|x| x.pk.to_s == pk}
|
163
|
-
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})") unless reflection[:nested_attributes][:strict] == false
|
164
|
-
end
|
165
|
-
obj
|
166
|
-
end
|
167
|
-
|
168
176
|
# Take an array or hash of attribute hashes and set each one individually.
|
169
177
|
# If a hash is provided it, sort it by key and then use the values.
|
170
178
|
# If there is a limit on the nested attributes for this association,
|
@@ -177,24 +185,22 @@ module Sequel
|
|
177
185
|
attributes_list.each{|a| nested_attributes_setter(reflection, a)}
|
178
186
|
end
|
179
187
|
|
180
|
-
# Remove the
|
181
|
-
#
|
188
|
+
# Remove the given associated object from the current object. If the
|
189
|
+
# :destroy option is given, destroy the object after disassociating it
|
182
190
|
# (unless destroying the object would automatically disassociate it).
|
183
|
-
# Returns the object removed
|
184
|
-
def nested_attributes_remove(reflection,
|
185
|
-
if
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
send(reflection.setter_method, nil)
|
192
|
-
end
|
191
|
+
# Returns the object removed.
|
192
|
+
def nested_attributes_remove(reflection, obj, opts={})
|
193
|
+
if !opts[:destroy] || reflection.remove_before_destroy?
|
194
|
+
before_save_hook do
|
195
|
+
if reflection.returns_array?
|
196
|
+
send(reflection.remove_method, obj)
|
197
|
+
else
|
198
|
+
send(reflection.setter_method, nil)
|
193
199
|
end
|
194
200
|
end
|
195
|
-
after_save_hook{obj.destroy} if opts[:destroy]
|
196
|
-
obj
|
197
201
|
end
|
202
|
+
after_save_hook{obj.destroy} if opts[:destroy]
|
203
|
+
obj
|
198
204
|
end
|
199
205
|
|
200
206
|
# Set the fields in the obj based on the association, only allowing
|
@@ -207,43 +213,57 @@ module Sequel
|
|
207
213
|
end
|
208
214
|
end
|
209
215
|
|
210
|
-
# Modify the associated object based on the contents of the
|
216
|
+
# Modify the associated object based on the contents of the attributes hash:
|
217
|
+
# * If a :transform block was given to nested_attributes, use it to modify the attribute hash.
|
211
218
|
# * If a block was given to nested_attributes, call it with the attributes and return immediately if the block returns true.
|
219
|
+
# * If a primary key exists in the attributes hash and it matches an associated object:
|
220
|
+
# ** If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
|
221
|
+
# ** If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
|
222
|
+
# ** Otherwise, update the matching associated object with the contents of the hash.
|
223
|
+
# * If a primary key exists in the attributes hash but it does not match an associated object,
|
224
|
+
# either raise an error, create a new object or ignore the hash, depending on the :unmatched_pk option.
|
212
225
|
# * If no primary key exists in the attributes hash, create a new object.
|
213
|
-
# * If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
|
214
|
-
# * If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
|
215
|
-
# * Otherwise, update the matching associated object with the contents of the hash.
|
216
226
|
def nested_attributes_setter(reflection, attributes)
|
227
|
+
if a = reflection[:nested_attributes][:transform]
|
228
|
+
attributes = a.call(self, attributes)
|
229
|
+
end
|
217
230
|
return if (b = reflection[:nested_attributes][:reject_if]) && b.call(attributes)
|
218
231
|
modified!
|
219
232
|
klass = reflection.associated_class
|
220
|
-
|
221
|
-
|
233
|
+
sym_keys = Array(klass.primary_key)
|
234
|
+
str_keys = sym_keys.map{|k| k.to_s}
|
235
|
+
if (pk = attributes.values_at(*sym_keys)).all? || (pk = attributes.values_at(*str_keys)).all?
|
236
|
+
pk = pk.map{|k| k.to_s}
|
237
|
+
obj = Array(send(reflection[:name])).find{|x| Array(x.pk).map{|k| k.to_s} == pk}
|
238
|
+
end
|
239
|
+
if obj
|
240
|
+
attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
|
222
241
|
if reflection[:nested_attributes][:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
|
223
|
-
nested_attributes_remove(reflection,
|
242
|
+
nested_attributes_remove(reflection, obj, :destroy=>true)
|
224
243
|
elsif reflection[:nested_attributes][:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
|
225
|
-
nested_attributes_remove(reflection,
|
244
|
+
nested_attributes_remove(reflection, obj)
|
226
245
|
else
|
227
|
-
nested_attributes_update(reflection,
|
246
|
+
nested_attributes_update(reflection, obj, attributes)
|
247
|
+
end
|
248
|
+
elsif pk.all? && reflection[:nested_attributes][:unmatched_pk] != :create
|
249
|
+
if reflection[:nested_attributes][:unmatched_pk] == :raise
|
250
|
+
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})")
|
228
251
|
end
|
229
252
|
else
|
230
253
|
nested_attributes_create(reflection, attributes)
|
231
254
|
end
|
232
255
|
end
|
233
256
|
|
234
|
-
# Update the
|
235
|
-
#
|
236
|
-
#
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
after_save_hook{obj.save_changes(:validate=>false)}
|
245
|
-
obj
|
246
|
-
end
|
257
|
+
# Update the given object with the attributes, validating it when the
|
258
|
+
# parent object is validated and saving it when the parent is saved.
|
259
|
+
# Returns the object updated.
|
260
|
+
def nested_attributes_update(reflection, obj, attributes)
|
261
|
+
nested_attributes_update_attributes(reflection, obj, attributes)
|
262
|
+
after_validation_hook{validate_associated_object(reflection, obj)}
|
263
|
+
# Don't need to validate the object twice if :validate association option is not false
|
264
|
+
# and don't want to validate it at all if it is false.
|
265
|
+
after_save_hook{obj.save_changes(:validate=>false)}
|
266
|
+
obj
|
247
267
|
end
|
248
268
|
|
249
269
|
# Update the attributes for the given object related to the current object through the association.
|
@@ -65,6 +65,14 @@ module Sequel
|
|
65
65
|
lc = model.lock_column
|
66
66
|
instance_filter(lc=>send(lc))
|
67
67
|
end
|
68
|
+
|
69
|
+
# Clear the instance filters when refreshing, so that attempting to
|
70
|
+
# refresh after a failed save removes the previous lock column filter
|
71
|
+
# (the new one will be added before updating).
|
72
|
+
def _refresh(ds)
|
73
|
+
clear_instance_filters
|
74
|
+
super
|
75
|
+
end
|
68
76
|
|
69
77
|
# Only update the row if it has the same lock version, and increment the
|
70
78
|
# lock version.
|
@@ -28,19 +28,18 @@ module Sequel
|
|
28
28
|
module TacticalEagerLoading
|
29
29
|
module InstanceMethods
|
30
30
|
# The dataset that retrieved this object, set if the object was
|
31
|
-
# reteived via Dataset#all
|
31
|
+
# reteived via Dataset#all.
|
32
32
|
attr_accessor :retrieved_by
|
33
33
|
|
34
34
|
# All model objects retrieved with this object, set if the object was
|
35
|
-
# reteived via Dataset#all
|
35
|
+
# reteived via Dataset#all.
|
36
36
|
attr_accessor :retrieved_with
|
37
37
|
|
38
38
|
private
|
39
39
|
|
40
|
-
# If there
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# current object.
|
40
|
+
# If there the association is not in the associations cache and the object
|
41
|
+
# was reteived via Dataset#all, eagerly load the association for all model
|
42
|
+
# objects retrieved with the current object.
|
44
43
|
def load_associated_objects(opts, reload=false)
|
45
44
|
name = opts[:name]
|
46
45
|
if !associations.include?(name) && retrieved_by
|
@@ -51,6 +50,7 @@ module Sequel
|
|
51
50
|
# is only defined in a subclass. This particular instance can use the
|
52
51
|
# association, but it can't be eagerly loaded as the parent class doesn't
|
53
52
|
# have access to the association, and that's the class doing the eager loading.
|
53
|
+
nil
|
54
54
|
end
|
55
55
|
end
|
56
56
|
super
|
@@ -60,7 +60,7 @@ module Sequel
|
|
60
60
|
module DatasetMethods
|
61
61
|
private
|
62
62
|
|
63
|
-
#
|
63
|
+
# Set the reteived_with and retrieved_by attributes for the object
|
64
64
|
# with the current dataset and array of all objects.
|
65
65
|
def post_load(objects)
|
66
66
|
super
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 3
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 36
|
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
|
@@ -60,12 +60,22 @@ describe "A PostgreSQL database" do
|
|
60
60
|
[:value, {:type=>:blob, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"bytea", :primary_key=>false}]
|
61
61
|
]
|
62
62
|
end
|
63
|
+
|
64
|
+
specify "should parse foreign keys for tables in a schema" do
|
65
|
+
begin
|
66
|
+
@db.create_table!(:public__testfk){primary_key :id; foreign_key :i, :public__testfk}
|
67
|
+
@db.foreign_key_list(:public__testfk).should == [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>Sequel.qualify(:public, :testfk), :name=>:testfk_i_fkey}]
|
68
|
+
ensure
|
69
|
+
@db.drop_table(:public__testfk)
|
70
|
+
end
|
71
|
+
end
|
63
72
|
end
|
64
73
|
|
65
74
|
describe "A PostgreSQL dataset" do
|
66
75
|
before do
|
67
76
|
@d = POSTGRES_DB[:test]
|
68
77
|
@d.delete # remove all records
|
78
|
+
POSTGRES_DB.sqls.clear
|
69
79
|
end
|
70
80
|
|
71
81
|
specify "should quote columns and tables using double quotes if quoting identifiers" do
|
@@ -148,6 +158,102 @@ describe "A PostgreSQL dataset" do
|
|
148
158
|
@d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
|
149
159
|
end
|
150
160
|
|
161
|
+
specify "should support :using when altering a column's type" do
|
162
|
+
begin
|
163
|
+
@db = POSTGRES_DB
|
164
|
+
@db.create_table!(:atest){Integer :t}
|
165
|
+
@db[:atest].insert(1262304000)
|
166
|
+
@db.alter_table(:atest){set_column_type :t, Time, :using=>'epoch'.cast(Time) + '1 second'.cast(:interval) * :t}
|
167
|
+
@db[:atest].get(:t.extract(:year)).should == 2010
|
168
|
+
ensure
|
169
|
+
@db.drop_table?(:atest)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
specify "should support :using with a string when altering a column's type" do
|
174
|
+
begin
|
175
|
+
@db = POSTGRES_DB
|
176
|
+
@db.create_table!(:atest){Integer :t}
|
177
|
+
@db[:atest].insert(1262304000)
|
178
|
+
@db.alter_table(:atest){set_column_type :t, Time, :using=>"'epoch'::timestamp + '1 second'::interval * t"}
|
179
|
+
@db[:atest].get(:t.extract(:year)).should == 2010
|
180
|
+
ensure
|
181
|
+
@db.drop_table?(:atest)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
specify "should have #transaction support various types of synchronous options" do
|
186
|
+
@db = POSTGRES_DB
|
187
|
+
@db.transaction(:synchronous=>:on){}
|
188
|
+
@db.transaction(:synchronous=>true){}
|
189
|
+
@db.transaction(:synchronous=>:off){}
|
190
|
+
@db.transaction(:synchronous=>false){}
|
191
|
+
@db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = on", "SET LOCAL synchronous_commit = on", "SET LOCAL synchronous_commit = off", "SET LOCAL synchronous_commit = off"]
|
192
|
+
|
193
|
+
@db.sqls.clear
|
194
|
+
@db.transaction(:synchronous=>nil){}
|
195
|
+
@db.sqls.should == ['BEGIN', 'COMMIT']
|
196
|
+
|
197
|
+
if @db.server_version >= 90100
|
198
|
+
@db.sqls.clear
|
199
|
+
@db.transaction(:synchronous=>:local){}
|
200
|
+
@db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = local"]
|
201
|
+
|
202
|
+
if @db.server_version >= 90200
|
203
|
+
@db.sqls.clear
|
204
|
+
@db.transaction(:synchronous=>:remote_write){}
|
205
|
+
@db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = remote_write"]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
specify "should have #transaction support read only transactions" do
|
211
|
+
@db = POSTGRES_DB
|
212
|
+
@db.transaction(:read_only=>true){}
|
213
|
+
@db.transaction(:read_only=>false){}
|
214
|
+
@db.transaction(:isolation=>:serializable, :read_only=>true){}
|
215
|
+
@db.transaction(:isolation=>:serializable, :read_only=>false){}
|
216
|
+
@db.sqls.grep(/READ/).should == ["SET TRANSACTION READ ONLY", "SET TRANSACTION READ WRITE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"]
|
217
|
+
end
|
218
|
+
|
219
|
+
specify "should have #transaction support deferrable transactions" do
|
220
|
+
@db = POSTGRES_DB
|
221
|
+
@db.transaction(:deferrable=>true){}
|
222
|
+
@db.transaction(:deferrable=>false){}
|
223
|
+
@db.transaction(:deferrable=>true, :read_only=>true){}
|
224
|
+
@db.transaction(:deferrable=>false, :read_only=>false){}
|
225
|
+
@db.transaction(:isolation=>:serializable, :deferrable=>true, :read_only=>true){}
|
226
|
+
@db.transaction(:isolation=>:serializable, :deferrable=>false, :read_only=>false){}
|
227
|
+
@db.sqls.grep(/DEF/).should == ["SET TRANSACTION DEFERRABLE", "SET TRANSACTION NOT DEFERRABLE", "SET TRANSACTION READ ONLY DEFERRABLE", "SET TRANSACTION READ WRITE NOT DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE NOT DEFERRABLE"]
|
228
|
+
end if POSTGRES_DB.server_version >= 90100
|
229
|
+
|
230
|
+
specify "should support creating indexes concurrently" do
|
231
|
+
POSTGRES_DB.sqls.clear
|
232
|
+
POSTGRES_DB.add_index :test, [:name, :value], :concurrently=>true
|
233
|
+
POSTGRES_DB.sqls.should == ['CREATE INDEX CONCURRENTLY "test_name_value_index" ON "test" ("name", "value")'] if check_sqls
|
234
|
+
end
|
235
|
+
|
236
|
+
specify "should support dropping indexes only if they already exist" do
|
237
|
+
POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv1'
|
238
|
+
POSTGRES_DB.sqls.clear
|
239
|
+
POSTGRES_DB.drop_index :test, [:name, :value], :if_exists=>true, :name=>'tnv1'
|
240
|
+
POSTGRES_DB.sqls.should == ['DROP INDEX IF EXISTS "tnv1"']
|
241
|
+
end
|
242
|
+
|
243
|
+
specify "should support CASCADE when dropping indexes" do
|
244
|
+
POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv2'
|
245
|
+
POSTGRES_DB.sqls.clear
|
246
|
+
POSTGRES_DB.drop_index :test, [:name, :value], :cascade=>true, :name=>'tnv2'
|
247
|
+
POSTGRES_DB.sqls.should == ['DROP INDEX "tnv2" CASCADE']
|
248
|
+
end
|
249
|
+
|
250
|
+
specify "should support dropping indexes concurrently" do
|
251
|
+
POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv2'
|
252
|
+
POSTGRES_DB.sqls.clear
|
253
|
+
POSTGRES_DB.drop_index :test, [:name, :value], :concurrently=>true, :name=>'tnv2'
|
254
|
+
POSTGRES_DB.sqls.should == ['DROP INDEX CONCURRENTLY "tnv2"']
|
255
|
+
end if POSTGRES_DB.server_version >= 90200
|
256
|
+
|
151
257
|
specify "#lock should lock table if inside a transaction" do
|
152
258
|
POSTGRES_DB.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
|
153
259
|
end
|
@@ -1188,7 +1294,7 @@ if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && POSTGRE
|
|
1188
1294
|
i = 0
|
1189
1295
|
@db.listen('foo2', :timeout=>0.001, :loop=>proc{i+=1; throw :stop if i > 3}){|ev, pid, payload| called = true}.should == nil
|
1190
1296
|
i.should == 4
|
1191
|
-
end unless
|
1297
|
+
end unless RUBY_PLATFORM =~ /mingw/ # Ruby freezes on this spec on this platform/version
|
1192
1298
|
end
|
1193
1299
|
end
|
1194
1300
|
|
@@ -1575,3 +1681,167 @@ describe 'PostgreSQL hstore handling' do
|
|
1575
1681
|
@ds.get(h2.avals.pg_array.length).should == 1
|
1576
1682
|
end
|
1577
1683
|
end if POSTGRES_DB.type_supported?(:hstore)
|
1684
|
+
|
1685
|
+
describe 'PostgreSQL json type' do
|
1686
|
+
before(:all) do
|
1687
|
+
Sequel.extension :pg_json
|
1688
|
+
@db = POSTGRES_DB
|
1689
|
+
@db.extend Sequel::Postgres::JSONDatabaseMethods
|
1690
|
+
@ds = @db[:items]
|
1691
|
+
@a = [1, 2, {'a'=>'b'}, 3.0]
|
1692
|
+
@h = {'a'=>'b', '1'=>[3, 4, 5]}
|
1693
|
+
@native = POSTGRES_DB.adapter_scheme == :postgres
|
1694
|
+
end
|
1695
|
+
after do
|
1696
|
+
@db.drop_table?(:items)
|
1697
|
+
end
|
1698
|
+
|
1699
|
+
specify 'insert and retrieve json values' do
|
1700
|
+
@db.create_table!(:items){json :j}
|
1701
|
+
@ds.insert(@h.pg_json)
|
1702
|
+
@ds.count.should == 1
|
1703
|
+
if @native
|
1704
|
+
rs = @ds.all
|
1705
|
+
v = rs.first[:j]
|
1706
|
+
v.should_not be_a_kind_of(Hash)
|
1707
|
+
v.to_hash.should be_a_kind_of(Hash)
|
1708
|
+
v.should == @h
|
1709
|
+
v.to_hash.should == @h
|
1710
|
+
@ds.delete
|
1711
|
+
@ds.insert(rs.first)
|
1712
|
+
@ds.all.should == rs
|
1713
|
+
end
|
1714
|
+
|
1715
|
+
@ds.delete
|
1716
|
+
@ds.insert(@a.pg_json)
|
1717
|
+
@ds.count.should == 1
|
1718
|
+
if @native
|
1719
|
+
rs = @ds.all
|
1720
|
+
v = rs.first[:j]
|
1721
|
+
v.should_not be_a_kind_of(Array)
|
1722
|
+
v.to_a.should be_a_kind_of(Array)
|
1723
|
+
v.should == @a
|
1724
|
+
v.to_a.should == @a
|
1725
|
+
@ds.delete
|
1726
|
+
@ds.insert(rs.first)
|
1727
|
+
@ds.all.should == rs
|
1728
|
+
end
|
1729
|
+
end
|
1730
|
+
|
1731
|
+
specify 'use json in bound variables' do
|
1732
|
+
@db.create_table!(:items){json :i}
|
1733
|
+
@ds.call(:insert, {:i=>@h.pg_json}, {:i=>:$i})
|
1734
|
+
@ds.get(:i).should == @h
|
1735
|
+
@ds.filter(:i.cast(String)=>:$i).call(:first, :i=>@h.pg_json).should == {:i=>@h}
|
1736
|
+
@ds.filter(:i.cast(String)=>:$i).call(:first, :i=>{}.pg_json).should == nil
|
1737
|
+
@ds.filter(:i.cast(String)=>:$i).call(:delete, :i=>@h.pg_json).should == 1
|
1738
|
+
|
1739
|
+
@ds.call(:insert, {:i=>@a.pg_json}, {:i=>:$i})
|
1740
|
+
@ds.get(:i).should == @a
|
1741
|
+
@ds.filter(:i.cast(String)=>:$i).call(:first, :i=>@a.pg_json).should == {:i=>@a}
|
1742
|
+
@ds.filter(:i.cast(String)=>:$i).call(:first, :i=>[].pg_json).should == nil
|
1743
|
+
end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
|
1744
|
+
|
1745
|
+
specify 'with models' do
|
1746
|
+
@db.create_table!(:items) do
|
1747
|
+
primary_key :id
|
1748
|
+
json :h
|
1749
|
+
end
|
1750
|
+
c = Class.new(Sequel::Model(@db[:items]))
|
1751
|
+
c.plugin :typecast_on_load, :h unless @native
|
1752
|
+
c.create(:h=>@h.pg_json).h.should == @h
|
1753
|
+
c.create(:h=>@a.pg_json).h.should == @a
|
1754
|
+
end
|
1755
|
+
end if POSTGRES_DB.server_version >= 90200
|
1756
|
+
|
1757
|
+
describe 'PostgreSQL inet/cidr types' do
|
1758
|
+
ipv6_broken = (IPAddr.new('::1'); false) rescue true
|
1759
|
+
|
1760
|
+
before(:all) do
|
1761
|
+
Sequel.extension :pg_inet
|
1762
|
+
@db = POSTGRES_DB
|
1763
|
+
@db.extend Sequel::Postgres::InetDatabaseMethods
|
1764
|
+
@ds = @db[:items]
|
1765
|
+
@v4 = '127.0.0.1'
|
1766
|
+
@v4nm = '127.0.0.0/8'
|
1767
|
+
@v6 = '2001:4f8:3:ba:2e0:81ff:fe22:d1f1'
|
1768
|
+
@v6nm = '2001:4f8:3:ba::/64'
|
1769
|
+
@ipv4 = IPAddr.new(@v4)
|
1770
|
+
@ipv4nm = IPAddr.new(@v4nm)
|
1771
|
+
unless ipv6_broken
|
1772
|
+
@ipv6 = IPAddr.new(@v6)
|
1773
|
+
@ipv6nm = IPAddr.new(@v6nm)
|
1774
|
+
end
|
1775
|
+
@native = POSTGRES_DB.adapter_scheme == :postgres
|
1776
|
+
end
|
1777
|
+
after do
|
1778
|
+
@db.drop_table?(:items)
|
1779
|
+
end
|
1780
|
+
|
1781
|
+
specify 'insert and retrieve inet/cidr values' do
|
1782
|
+
@db.create_table!(:items){inet :i; cidr :c}
|
1783
|
+
@ds.insert(@ipv4, @ipv4nm)
|
1784
|
+
@ds.count.should == 1
|
1785
|
+
if @native
|
1786
|
+
rs = @ds.all
|
1787
|
+
rs.first[:i].should == @ipv4
|
1788
|
+
rs.first[:c].should == @ipv4nm
|
1789
|
+
rs.first[:i].should be_a_kind_of(IPAddr)
|
1790
|
+
rs.first[:c].should be_a_kind_of(IPAddr)
|
1791
|
+
@ds.delete
|
1792
|
+
@ds.insert(rs.first)
|
1793
|
+
@ds.all.should == rs
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
unless ipv6_broken
|
1797
|
+
@ds.delete
|
1798
|
+
@ds.insert(@ipv6, @ipv6nm)
|
1799
|
+
@ds.count.should == 1
|
1800
|
+
if @native
|
1801
|
+
rs = @ds.all
|
1802
|
+
v = rs.first[:j]
|
1803
|
+
rs.first[:i].should == @ipv6
|
1804
|
+
rs.first[:c].should == @ipv6nm
|
1805
|
+
rs.first[:i].should be_a_kind_of(IPAddr)
|
1806
|
+
rs.first[:c].should be_a_kind_of(IPAddr)
|
1807
|
+
@ds.delete
|
1808
|
+
@ds.insert(rs.first)
|
1809
|
+
@ds.all.should == rs
|
1810
|
+
end
|
1811
|
+
end
|
1812
|
+
end
|
1813
|
+
|
1814
|
+
specify 'use ipaddr in bound variables' do
|
1815
|
+
@db.create_table!(:items){inet :i; cidr :c}
|
1816
|
+
|
1817
|
+
@ds.call(:insert, {:i=>@ipv4, :c=>@ipv4nm}, {:i=>:$i, :c=>:$c})
|
1818
|
+
@ds.get(:i).should == @ipv4
|
1819
|
+
@ds.get(:c).should == @ipv4nm
|
1820
|
+
@ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == {:i=>@ipv4, :c=>@ipv4nm}
|
1821
|
+
@ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv6, :c=>@ipv6nm).should == nil
|
1822
|
+
@ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv4, :c=>@ipv4nm).should == 1
|
1823
|
+
|
1824
|
+
unless ipv6_broken
|
1825
|
+
@ds.call(:insert, {:i=>@ipv6, :c=>@ipv6nm}, {:i=>:$i, :c=>:$c})
|
1826
|
+
@ds.get(:i).should == @ipv6
|
1827
|
+
@ds.get(:c).should == @ipv6nm
|
1828
|
+
@ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv6, :c=>@ipv6nm).should == {:i=>@ipv6, :c=>@ipv6nm}
|
1829
|
+
@ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == nil
|
1830
|
+
@ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv6, :c=>@ipv6nm).should == 1
|
1831
|
+
end
|
1832
|
+
end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
|
1833
|
+
|
1834
|
+
specify 'with models' do
|
1835
|
+
@db.create_table!(:items) do
|
1836
|
+
primary_key :id
|
1837
|
+
inet :i
|
1838
|
+
cidr :c
|
1839
|
+
end
|
1840
|
+
c = Class.new(Sequel::Model(@db[:items]))
|
1841
|
+
c.plugin :typecast_on_load, :i, :c unless @native
|
1842
|
+
c.create(:i=>@v4, :c=>@v4nm).values.values_at(:i, :c).should == [@ipv4, @ipv4nm]
|
1843
|
+
unless ipv6_broken
|
1844
|
+
c.create(:i=>@ipv6, :c=>@ipv6nm).values.values_at(:i, :c).should == [@ipv6, @ipv6nm]
|
1845
|
+
end
|
1846
|
+
end
|
1847
|
+
end if POSTGRES_DB.server_version >= 90200
|