sequel 3.35.0 → 3.36.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +78 -0
  2. data/Rakefile +3 -3
  3. data/bin/sequel +3 -1
  4. data/doc/advanced_associations.rdoc +154 -11
  5. data/doc/migration.rdoc +18 -0
  6. data/doc/object_model.rdoc +541 -0
  7. data/doc/opening_databases.rdoc +4 -1
  8. data/doc/release_notes/3.36.0.txt +245 -0
  9. data/doc/schema_modification.rdoc +0 -6
  10. data/lib/sequel/adapters/do/mysql.rb +7 -0
  11. data/lib/sequel/adapters/jdbc.rb +11 -3
  12. data/lib/sequel/adapters/jdbc/mysql.rb +3 -5
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +10 -8
  14. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  15. data/lib/sequel/adapters/mock.rb +2 -6
  16. data/lib/sequel/adapters/mysql.rb +3 -9
  17. data/lib/sequel/adapters/mysql2.rb +12 -11
  18. data/lib/sequel/adapters/postgres.rb +32 -40
  19. data/lib/sequel/adapters/shared/mssql.rb +15 -11
  20. data/lib/sequel/adapters/shared/mysql.rb +28 -3
  21. data/lib/sequel/adapters/shared/oracle.rb +5 -0
  22. data/lib/sequel/adapters/shared/postgres.rb +59 -5
  23. data/lib/sequel/adapters/shared/sqlite.rb +3 -13
  24. data/lib/sequel/adapters/sqlite.rb +0 -7
  25. data/lib/sequel/adapters/swift/mysql.rb +2 -5
  26. data/lib/sequel/adapters/tinytds.rb +1 -2
  27. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  28. data/lib/sequel/connection_pool/threaded.rb +9 -1
  29. data/lib/sequel/database/dataset_defaults.rb +3 -1
  30. data/lib/sequel/database/misc.rb +7 -1
  31. data/lib/sequel/database/query.rb +11 -3
  32. data/lib/sequel/database/schema_generator.rb +40 -9
  33. data/lib/sequel/database/schema_methods.rb +6 -1
  34. data/lib/sequel/dataset/actions.rb +5 -5
  35. data/lib/sequel/dataset/prepared_statements.rb +3 -1
  36. data/lib/sequel/dataset/query.rb +1 -1
  37. data/lib/sequel/extensions/migration.rb +28 -0
  38. data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -9
  39. data/lib/sequel/extensions/pg_inet.rb +89 -0
  40. data/lib/sequel/extensions/pg_json.rb +178 -0
  41. data/lib/sequel/extensions/schema_dumper.rb +24 -6
  42. data/lib/sequel/model/associations.rb +19 -15
  43. data/lib/sequel/model/base.rb +11 -12
  44. data/lib/sequel/plugins/composition.rb +1 -2
  45. data/lib/sequel/plugins/eager_each.rb +59 -0
  46. data/lib/sequel/plugins/json_serializer.rb +41 -4
  47. data/lib/sequel/plugins/nested_attributes.rb +72 -52
  48. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  49. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +271 -1
  52. data/spec/adapters/sqlite_spec.rb +11 -0
  53. data/spec/core/connection_pool_spec.rb +26 -1
  54. data/spec/core/database_spec.rb +19 -0
  55. data/spec/core/dataset_spec.rb +45 -5
  56. data/spec/core/expression_filters_spec.rb +31 -67
  57. data/spec/core/mock_adapter_spec.rb +4 -0
  58. data/spec/extensions/core_extensions_spec.rb +83 -0
  59. data/spec/extensions/eager_each_spec.rb +34 -0
  60. data/spec/extensions/inflector_spec.rb +0 -4
  61. data/spec/extensions/json_serializer_spec.rb +32 -1
  62. data/spec/extensions/migration_spec.rb +28 -0
  63. data/spec/extensions/nested_attributes_spec.rb +134 -1
  64. data/spec/extensions/optimistic_locking_spec.rb +15 -1
  65. data/spec/extensions/pg_hstore_spec.rb +1 -1
  66. data/spec/extensions/pg_inet_spec.rb +44 -0
  67. data/spec/extensions/pg_json_spec.rb +101 -0
  68. data/spec/extensions/prepared_statements_spec.rb +30 -0
  69. data/spec/extensions/rcte_tree_spec.rb +9 -0
  70. data/spec/extensions/schema_dumper_spec.rb +195 -7
  71. data/spec/extensions/serialization_spec.rb +4 -0
  72. data/spec/extensions/spec_helper.rb +9 -1
  73. data/spec/extensions/tactical_eager_loading_spec.rb +8 -0
  74. data/spec/integration/database_test.rb +5 -1
  75. data/spec/integration/prepared_statement_test.rb +20 -2
  76. data/spec/model/associations_spec.rb +27 -0
  77. data/spec/model/base_spec.rb +54 -0
  78. data/spec/model/model_spec.rb +6 -0
  79. data/spec/model/record_spec.rb +18 -0
  80. data/spec/rcov.opts +2 -0
  81. 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 - Set to false to not raise an error message if a primary key
93
- # is provided in a record, but it doesn't match an existing associated
94
- # object.
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 passed each nested attribute hash. If
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
- raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed") unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
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 matching associated object from the current object.
181
- # If the :destroy option is given, destroy the object after disassociating it
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, if it exists.
184
- def nested_attributes_remove(reflection, pk, opts={})
185
- if obj = nested_attributes_find(reflection, pk)
186
- if !opts[:destroy] || reflection.remove_before_destroy?
187
- before_save_hook do
188
- if reflection.returns_array?
189
- send(reflection.remove_method, obj)
190
- else
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 attribtues hash:
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
- if pk = attributes.delete(klass.primary_key) || attributes.delete(klass.primary_key.to_s)
221
- attributes = attributes.dup
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, pk, :destroy=>true)
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, pk)
244
+ nested_attributes_remove(reflection, obj)
226
245
  else
227
- nested_attributes_update(reflection, pk, attributes)
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 matching associated object with the attributes,
235
- # validating it when the parent object is validated and saving it
236
- # when the parent is saved.
237
- # Returns the object updated, if it exists.
238
- def nested_attributes_update(reflection, pk, attributes)
239
- if obj = nested_attributes_find(reflection, pk)
240
- nested_attributes_update_attributes(reflection, obj, attributes)
241
- after_validation_hook{validate_associated_object(reflection, obj)}
242
- # Don't need to validate the object twice if :validate association option is not false
243
- # and don't want to validate it at all if it is false.
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 with an active identity map.
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 with an active identity map.
35
+ # reteived via Dataset#all.
36
36
  attr_accessor :retrieved_with
37
37
 
38
38
  private
39
39
 
40
- # If there is an active identity map and the association is not in the
41
- # associations cache and the object was reteived via Dataset#all,
42
- # eagerly load the association for all model objects retrieved with the
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
- # If there is an active identity map, set the reteived_with attribute for the object
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
@@ -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 = 35
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 RUBY_VERSION =~ /1.8.7|1.9.2/ && RUBY_PLATFORM =~ /mingw/ # Ruby freezes on this spec on this platform/version
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