sequel 4.14.0 → 4.15.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/README.rdoc +3 -3
  4. data/Rakefile +1 -1
  5. data/doc/opening_databases.rdoc +20 -2
  6. data/doc/release_notes/4.15.0.txt +56 -0
  7. data/doc/testing.rdoc +10 -4
  8. data/lib/sequel/adapters/fdbsql.rb +285 -0
  9. data/lib/sequel/adapters/informix.rb +15 -0
  10. data/lib/sequel/adapters/jdbc/fdbsql.rb +65 -0
  11. data/lib/sequel/adapters/mock.rb +1 -0
  12. data/lib/sequel/adapters/shared/fdbsql.rb +550 -0
  13. data/lib/sequel/adapters/shared/postgres.rb +23 -10
  14. data/lib/sequel/database/connecting.rb +1 -1
  15. data/lib/sequel/database/schema_methods.rb +10 -3
  16. data/lib/sequel/dataset/placeholder_literalizer.rb +7 -0
  17. data/lib/sequel/extensions/date_arithmetic.rb +5 -0
  18. data/lib/sequel/extensions/migration.rb +2 -2
  19. data/lib/sequel/extensions/pg_array.rb +15 -1
  20. data/lib/sequel/extensions/pg_json.rb +3 -0
  21. data/lib/sequel/extensions/pg_json_ops.rb +4 -4
  22. data/lib/sequel/extensions/schema_dumper.rb +9 -1
  23. data/lib/sequel/model/associations.rb +70 -21
  24. data/lib/sequel/plugins/active_model.rb +7 -2
  25. data/lib/sequel/plugins/many_through_many.rb +1 -0
  26. data/lib/sequel/plugins/pg_array_associations.rb +2 -1
  27. data/lib/sequel/plugins/split_values.rb +64 -0
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/fdbsql_spec.rb +429 -0
  30. data/spec/adapters/informix_spec.rb +6 -0
  31. data/spec/adapters/postgres_spec.rb +49 -1
  32. data/spec/adapters/spec_helper.rb +6 -1
  33. data/spec/adapters/sqlite_spec.rb +1 -1
  34. data/spec/core/placeholder_literalizer_spec.rb +10 -0
  35. data/spec/extensions/date_arithmetic_spec.rb +7 -0
  36. data/spec/extensions/many_through_many_spec.rb +14 -0
  37. data/spec/extensions/migration_spec.rb +3 -3
  38. data/spec/extensions/pg_array_associations_spec.rb +9 -0
  39. data/spec/extensions/pg_json_ops_spec.rb +4 -8
  40. data/spec/extensions/schema_dumper_spec.rb +9 -0
  41. data/spec/extensions/spec_helper.rb +3 -0
  42. data/spec/extensions/split_values_spec.rb +22 -0
  43. data/spec/integration/database_test.rb +1 -1
  44. data/spec/integration/dataset_test.rb +1 -1
  45. data/spec/integration/eager_loader_test.rb +1 -1
  46. data/spec/integration/plugin_test.rb +3 -2
  47. data/spec/integration/prepared_statement_test.rb +3 -3
  48. data/spec/integration/schema_test.rb +3 -3
  49. data/spec/integration/spec_helper.rb +6 -1
  50. data/spec/integration/timezone_test.rb +1 -1
  51. data/spec/model/association_reflection_spec.rb +29 -0
  52. data/spec/model/associations_spec.rb +36 -0
  53. data/spec/model/eager_loading_spec.rb +14 -0
  54. data/spec/model/spec_helper.rb +3 -0
  55. data/spec/rspec_helper.rb +4 -0
  56. metadata +10 -2
@@ -240,7 +240,7 @@ module Sequel
240
240
 
241
241
  # The primary key of the associated model.
242
242
  def primary_key
243
- cached_fetch(:primary_key){associated_class.primary_key}
243
+ cached_fetch(:primary_key){associated_class.primary_key || raise(Error, "no primary key specified for #{associated_class.inspect}")}
244
244
  end
245
245
 
246
246
  # The method to call to get value of the primary key of the associated model.
@@ -301,6 +301,7 @@ module Sequel
301
301
  name = opts[:name]
302
302
  model = self
303
303
  pk = opts[:eager_loader_key] = opts[:primary_key] ||= model.primary_key
304
+ raise(Error, "no primary key specified for #{inspect}") unless pk
304
305
  opts[:key] = opts.default_key unless opts.has_key?(:key)
305
306
  key = opts[:key]
306
307
  key_column = opts[:key_column] ||= opts[:key]
@@ -0,0 +1,64 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The split_values plugin splits the values hash retreived from the
4
+ # database, and moves keys from the values hash that are not columns
5
+ # in the model's dataset to a separate hash. This makes it so the
6
+ # values hash only stores columns from the model's dataset.
7
+ #
8
+ # Among other things, this allows you to save model objects even if
9
+ # they were retrieved with additional columns, and have equality
10
+ # comparisons with other instances not care about non-column values.
11
+ #
12
+ # Example:
13
+ #
14
+ # class Album < Sequel::Model
15
+ # plugin :split_values
16
+ # end
17
+ # a1 = Album[1]
18
+ # a2 = Album.select_append(Sequel.as(true, :exists))[1]
19
+ # a1.name # => 'Album Name'
20
+ # a2.name # => 'Album Name'
21
+ # a1[:exists] # => nil
22
+ # a2[:exists] # => true
23
+ # a1 == a2 # => true
24
+ # a2.values # => {:id=>1, :name=>'Album Name'}
25
+ # a2.save # Works
26
+ #
27
+ # Usage:
28
+ #
29
+ # # Make all model subclass instances split values
30
+ # # (called before loading subclasses)
31
+ # Sequel::Model.plugin :split_values
32
+ #
33
+ # # Make the Album class split values
34
+ # Album.plugin :split_values
35
+ module SplitValues
36
+ module ClassMethods
37
+ # Split the noncolumn values when creating a new object retrieved from
38
+ # the database.
39
+ def call(_)
40
+ super.split_noncolumn_values
41
+ end
42
+ end
43
+
44
+ module InstanceMethods
45
+ # If there isn't an entry in the values hash, but there is a noncolumn_values
46
+ # hash, look in that hash for the value.
47
+ def [](k)
48
+ super || (@noncolumn_values[k] if !@values.has_key?(k) && @noncolumn_values)
49
+ end
50
+
51
+ # Check all entries in the values hash. If any of the keys are not columns,
52
+ # move the entry into the noncolumn_values hash.
53
+ def split_noncolumn_values
54
+ @values.keys.each do |k|
55
+ unless columns.include?(k)
56
+ (@noncolumn_values ||= {})[k] = @values.delete(k)
57
+ end
58
+ end
59
+ self
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 14
6
+ MINOR = 15
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
@@ -0,0 +1,429 @@
1
+ SEQUEL_ADAPTER_TEST = :fdbsql
2
+
3
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
4
+
5
+ describe 'Fdbsql' do
6
+ describe 'Database' do
7
+ before(:all) do
8
+ @db = DB
9
+ end
10
+
11
+ describe 'schema_parsing' do
12
+ after do
13
+ @db.drop_table?(:test)
14
+ end
15
+
16
+ specify 'without primary key' do
17
+ @db.create_table(:test) do
18
+ text :name
19
+ int :value
20
+ end
21
+ schema = DB.schema(:test, :reload => true)
22
+ schema.count.should == 2
23
+ schema[0][0].should == :name
24
+ schema[1][0].should == :value
25
+ schema.each {|col| col[1][:primary_key].should == nil}
26
+ end
27
+
28
+ specify 'with one primary key' do
29
+ @db.create_table(:test) do
30
+ text :name
31
+ primary_key :id
32
+ end
33
+ schema = DB.schema(:test, :reload => true)
34
+ schema.count.should == 2
35
+ id_col = schema[0]
36
+ name_col = schema[1]
37
+ name_col[0].should == :name
38
+ id_col[0].should == :id
39
+ name_col[1][:primary_key].should == nil
40
+ id_col[1][:primary_key].should == true
41
+ end
42
+
43
+ specify 'with multiple primary keys' do
44
+ @db.create_table(:test) do
45
+ Integer :id
46
+ Integer :id2
47
+ primary_key [:id, :id2]
48
+ end
49
+ schema = DB.schema(:test, :reload => true)
50
+ schema.count.should == 2
51
+ id_col = schema[0]
52
+ id2_col = schema[1]
53
+ id_col[0].should == :id
54
+ id2_col[0].should == :id2
55
+ id_col[1][:primary_key].should == true
56
+ id2_col[1][:primary_key].should == true
57
+ end
58
+
59
+ specify 'with other constraints' do
60
+ @db.create_table(:test) do
61
+ primary_key :id
62
+ Integer :unique, :unique => true
63
+ end
64
+ schema = DB.schema(:test, :reload => true)
65
+ schema.count.should == 2
66
+ id_col = schema[0]
67
+ unique_col = schema[1]
68
+ id_col[0].should == :id
69
+ unique_col[0].should == :unique
70
+ id_col[1][:primary_key].should == true
71
+ unique_col[1][:primary_key].should == nil
72
+ end
73
+ after do
74
+ @db.drop_table?(:other_table)
75
+ end
76
+ specify 'with other tables' do
77
+ @db.create_table(:test) do
78
+ Integer :id
79
+ text :name
80
+ end
81
+ @db.create_table(:other_table) do
82
+ primary_key :id
83
+ varchar :name, :unique => true
84
+ end
85
+ schema = DB.schema(:test, :reload => true)
86
+ schema.count.should == 2
87
+ schema.each {|col| col[1][:primary_key].should == nil}
88
+ end
89
+
90
+ describe 'with explicit schema' do
91
+ before do
92
+ @db.create_table(:test) do
93
+ primary_key :id
94
+ end
95
+ @schema = @db['SELECT CURRENT_SCHEMA'].first.values.first
96
+ @second_schema = @schema + "--2"
97
+ @db.create_table(Sequel.qualify(@second_schema,:test)) do
98
+ primary_key :id2
99
+ Integer :id
100
+ end
101
+ end
102
+ after do
103
+ @db.drop_table?(Sequel.qualify(@second_schema,:test))
104
+ @db.drop_table?(:test)
105
+ end
106
+
107
+ specify 'gets info for correct table' do
108
+ schema = DB.schema(:test, :reload => true, :schema => @second_schema)
109
+ schema.count.should == 2
110
+ id2_col = schema[0]
111
+ id_col = schema[1]
112
+ id_col[0].should == :id
113
+ id2_col[0].should == :id2
114
+ id_col[1][:primary_key].should == nil
115
+ id2_col[1][:primary_key].should == true
116
+ end
117
+ end
118
+ end
119
+
120
+ describe 'primary_key' do
121
+ after do
122
+ @db.drop_table?(:test)
123
+ @db.drop_table?(:other_table)
124
+ end
125
+
126
+ specify 'without primary key' do
127
+ @db.create_table(:test) do
128
+ text :name
129
+ int :value
130
+ end
131
+ DB.primary_key(:test).should == nil
132
+ end
133
+
134
+ specify 'with one primary key' do
135
+ @db.create_table(:test) do
136
+ text :name
137
+ primary_key :id
138
+ end
139
+ DB.primary_key(:test).should == :id
140
+ end
141
+
142
+ specify 'with multiple primary keys' do
143
+ @db.create_table(:test) do
144
+ Integer :id
145
+ Integer :id2
146
+ primary_key [:id, :id2]
147
+ end
148
+ DB.primary_key(:test).should == [:id, :id2]
149
+ end
150
+
151
+ specify 'with other constraints' do
152
+ @db.create_table(:test) do
153
+ primary_key :id
154
+ Integer :unique, :unique => true
155
+ end
156
+ DB.primary_key(:test).should == :id
157
+ end
158
+
159
+ specify 'with other tables' do
160
+ @db.create_table(:test) do
161
+ Integer :id
162
+ text :name
163
+ end
164
+ @db.create_table(:other_table) do
165
+ primary_key :id
166
+ varchar :name, :unique => true
167
+ end
168
+ DB.primary_key(:other_table).should == :id
169
+ end
170
+
171
+ specify 'responds to alter table' do
172
+ @db.create_table(:test) do
173
+ Integer :id
174
+ text :name
175
+ end
176
+ @db.alter_table(:test) do
177
+ add_primary_key :quid
178
+ end
179
+ DB.primary_key(:test).should == :quid
180
+ end
181
+
182
+ describe 'with explicit schema' do
183
+ before do
184
+ @db.create_table(:test) do
185
+ primary_key :id
186
+ end
187
+ @schema = @db['SELECT CURRENT_SCHEMA'].first.values.first
188
+ @second_schema = @schema + "--2"
189
+ @db.create_table(Sequel.qualify(@second_schema,:test)) do
190
+ primary_key :id2
191
+ end
192
+ end
193
+ after do
194
+ @db.drop_table?(Sequel.qualify(@second_schema,:test))
195
+ @db.drop_table?(:test)
196
+ end
197
+
198
+ specify 'gets correct primary key' do
199
+ DB.primary_key(:test, :schema => @second_schema).should == :id2
200
+ end
201
+ end
202
+ end
203
+
204
+ describe '#tables' do
205
+ before do
206
+ @schema = @db['SELECT CURRENT_SCHEMA'].first.values.first
207
+ @second_schema = @schema + "--2"
208
+ @db.create_table(:test) do
209
+ primary_key :id
210
+ end
211
+ @db.create_table(Sequel.qualify(@second_schema,:test2)) do
212
+ primary_key :id
213
+ end
214
+ end
215
+ after do
216
+ @db.drop_table?(Sequel.qualify(@second_schema,:test2))
217
+ @db.drop_table?(:test)
218
+ end
219
+ specify 'on explicit schema' do
220
+ tables = @db.tables(:schema => @second_schema)
221
+ tables.should include(:test2)
222
+ tables.should_not include(:test)
223
+ end
224
+ specify 'qualified' do
225
+ tables = @db.tables(:qualify => true)
226
+ tables.should include(Sequel::SQL::QualifiedIdentifier.new(@schema.to_sym, :test))
227
+ tables.should_not include(:test)
228
+ end
229
+ end
230
+
231
+ describe '#views' do
232
+ def drop_things
233
+ @db.drop_view(Sequel.qualify(@second_schema,:test_view2), :if_exists => true)
234
+ @db.drop_table?(Sequel.qualify(@second_schema,:test_table))
235
+ @db.drop_view(:test_view, :if_exists => true)
236
+ @db.drop_table?(:test_table)
237
+ end
238
+ before do
239
+ @schema = @db['SELECT CURRENT_SCHEMA'].single_value
240
+ @second_schema = @schema + "--2"
241
+ drop_things
242
+ @db.create_table(:test_table){Integer :a}
243
+ @db.create_view :test_view, @db[:test_table]
244
+ @db.create_table(Sequel.qualify(@second_schema,:test_table)) do
245
+ Integer :b
246
+ end
247
+ @db.create_view(Sequel.qualify(@second_schema, :test_view2),
248
+ @db[Sequel.qualify(@second_schema, :test_table)])
249
+ end
250
+ after do
251
+ drop_things
252
+ end
253
+ specify 'on explicit schema' do
254
+ views = @db.views(:schema => @second_schema)
255
+ views.should include(:test_view2)
256
+ views.should_not include(:test_view)
257
+ end
258
+ specify 'qualified' do
259
+ views = @db.views(:qualify => true)
260
+ views.should include(Sequel::SQL::QualifiedIdentifier.new(@schema.to_sym, :test_view))
261
+ views.should_not include(:test)
262
+ end
263
+ end
264
+
265
+ describe 'prepared statements' do
266
+ def create_table
267
+ @db.create_table!(:test) {Integer :a; Text :b}
268
+ @db[:test].insert(1, 'blueberries')
269
+ @db[:test].insert(2, 'trucks')
270
+ @db[:test].insert(3, 'foxes')
271
+ end
272
+ def drop_table
273
+ @db.drop_table?(:test)
274
+ end
275
+ before do
276
+ create_table
277
+ end
278
+ after do
279
+ drop_table
280
+ end
281
+
282
+ it 're-prepares on stale statement' do
283
+ @db[:test].filter(:a=>:$n).prepare(:all, :select_a).call(:n=>2).to_a.should == [{:a => 2, :b => 'trucks'}]
284
+ drop_table
285
+ create_table
286
+ @db[:test].filter(:a=>:$n).prepare(:all, :select_a).call(:n=>2).to_a.should == [{:a => 2, :b => 'trucks'}]
287
+ end
288
+
289
+ it 'can call already prepared' do
290
+ @db[:test].filter(:a=>:$n).prepare(:all, :select_a).call(:n=>2).to_a.should == [{:a => 2, :b => 'trucks'}]
291
+ drop_table
292
+ create_table
293
+ @db.call(:select_a, :n=>2).to_a.should == [{:a => 2, :b => 'trucks'}]
294
+ end
295
+ end
296
+
297
+ describe 'Database schema modifiers' do
298
+ # this test was copied from sequel's integration/schema_test because that one drops a serial primary key which is not
299
+ # currently supported in fdbsql
300
+ specify "should be able to specify constraint names for column constraints" do
301
+ @db.create_table!(:items2){Integer :id, :primary_key=>true, :primary_key_constraint_name=>:foo_pk}
302
+ @db.create_table!(:items){foreign_key :id, :items2, :unique=>true, :foreign_key_constraint_name => :foo_fk, :unique_constraint_name => :foo_uk, :null=>false}
303
+ @db.alter_table(:items){drop_constraint :foo_fk, :type=>:foreign_key; drop_constraint :foo_uk, :type=>:unique}
304
+ @db.alter_table(:items2){drop_constraint :foo_pk, :type=>:primary_key}
305
+ end
306
+ end
307
+ end
308
+
309
+ describe 'Dataset' do
310
+ before(:all) do
311
+ @db = DB
312
+ end
313
+
314
+ describe 'provides_accurate_rows_matched' do
315
+ before do
316
+ DB.create_table!(:test) {Integer :a}
317
+ DB[:test].insert(1)
318
+ DB[:test].insert(2)
319
+ DB[:test].insert(3)
320
+ DB[:test].insert(4)
321
+ DB[:test].insert(5)
322
+ end
323
+
324
+ after do
325
+ DB.drop_table?(:test)
326
+ end
327
+
328
+ specify '#delete' do
329
+ DB[:test].where(:a => 8..10).delete.should == 0
330
+ DB[:test].where(:a => 5).delete.should == 1
331
+ DB[:test].where(:a => 1..3).delete.should == 3
332
+ end
333
+
334
+ specify '#update' do
335
+ DB[:test].where(:a => 8..10).update(:a => Sequel.+(:a, 10)).should == 0
336
+ DB[:test].where(:a => 5).update(:a => Sequel.+(:a, 1000)).should == 1
337
+ DB[:test].where(:a => 1..3).update(:a => Sequel.+(:a, 100)).should == 3
338
+ end
339
+
340
+ end
341
+
342
+ describe 'intersect and except ALL' do
343
+ before do
344
+ DB.create_table!(:test) {Integer :a; Integer :b}
345
+ DB[:test].insert(1, 10)
346
+ DB[:test].insert(2, 10)
347
+ DB[:test].insert(8, 15)
348
+ DB[:test].insert(2, 10)
349
+ DB[:test].insert(2, 10)
350
+ DB[:test].insert(1, 10)
351
+
352
+ DB.create_table!(:test2) {Integer :a; Integer :b}
353
+ DB[:test2].insert(1, 10)
354
+ DB[:test2].insert(2, 10)
355
+ DB[:test2].insert(2, 12)
356
+ DB[:test2].insert(3, 10)
357
+ DB[:test2].insert(1, 10)
358
+ end
359
+
360
+ after do
361
+ DB.drop_table?(:test)
362
+ DB.drop_table?(:test2)
363
+ end
364
+
365
+ specify 'intersect all' do
366
+ @db[:test].intersect(@db[:test2], :all => true).map{|r| [r[:a],r[:b]]}.to_a.sort.should == [[1, 10], [1,10], [2, 10]]
367
+ end
368
+
369
+ specify 'except all' do
370
+ @db[:test].except(@db[:test2], :all => true).map{|r| [r[:a],r[:b]]}.to_a.sort.should == [[2,10], [2, 10], [8,15]]
371
+ end
372
+ end
373
+
374
+ describe 'is' do
375
+ before do
376
+ DB.create_table!(:test) {Integer :a; Boolean :b}
377
+ DB[:test].insert(1, nil)
378
+ DB[:test].insert(2, true)
379
+ DB[:test].insert(3, false)
380
+ end
381
+ after do
382
+ DB.drop_table?(:test)
383
+ end
384
+
385
+ specify 'true' do
386
+ DB[:test].select(:a).where(Sequel::SQL::ComplexExpression.new(:IS, :b, true)).map{|r| r[:a]}.should == [2]
387
+ end
388
+
389
+ specify 'not true' do
390
+ DB[:test].select(:a).where(Sequel::SQL::ComplexExpression.new(:'IS NOT', :b, true)).map{|r| r[:a]}.should == [1, 3]
391
+ end
392
+ end
393
+
394
+ describe 'insert empty values' do
395
+ before do
396
+ DB.create_table!(:test) {primary_key :a}
397
+ end
398
+ after do
399
+ DB.drop_table?(:test)
400
+ end
401
+
402
+ specify 'inserts defaults and returns pk' do
403
+ DB[:test].insert().should == 1 # 1 should be the pk
404
+ end
405
+ end
406
+
407
+ describe 'function names' do
408
+ before do
409
+ DB.create_table!(:test) {Text :a; Text :b}
410
+ DB[:test].insert('1', '')
411
+ DB[:test].insert('2', 'trucks')
412
+ DB[:test].insert('3', 'foxes')
413
+ end
414
+ after do
415
+ DB.drop_table?(:test)
416
+ end
417
+
418
+ specify 'evaluate' do
419
+ DB[:test].select(Sequel.function(:now)).count == 1
420
+ DB[:test].select(Sequel.as(Sequel.function(:concat, :a, :b), :c)).map{|r| r[:c]}.should == ['1','2trucks','3foxes']
421
+ end
422
+
423
+ specify 'get quoted' do
424
+ DB[:test].select(Sequel.function(:now).quoted).sql.should =~ /"now"\(\)/
425
+ DB[:test].select(Sequel.as(Sequel.function(:concat, :a, :b).quoted, :c)).sql.should =~ /"concat"\("a", "b"\)/
426
+ end
427
+ end
428
+ end
429
+ end