sequel 3.23.0 → 3.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG +64 -0
  2. data/doc/association_basics.rdoc +43 -5
  3. data/doc/model_hooks.rdoc +64 -27
  4. data/doc/prepared_statements.rdoc +8 -4
  5. data/doc/reflection.rdoc +8 -2
  6. data/doc/release_notes/3.23.0.txt +1 -1
  7. data/doc/release_notes/3.24.0.txt +420 -0
  8. data/lib/sequel/adapters/db2.rb +8 -1
  9. data/lib/sequel/adapters/firebird.rb +25 -9
  10. data/lib/sequel/adapters/informix.rb +4 -19
  11. data/lib/sequel/adapters/jdbc.rb +34 -17
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  14. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  15. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  16. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  18. data/lib/sequel/adapters/postgres.rb +30 -1
  19. data/lib/sequel/adapters/shared/access.rb +10 -0
  20. data/lib/sequel/adapters/shared/informix.rb +45 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +82 -8
  22. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  23. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  24. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  25. data/lib/sequel/adapters/sqlite.rb +8 -3
  26. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  27. data/lib/sequel/ast_transformer.rb +190 -0
  28. data/lib/sequel/core.rb +1 -1
  29. data/lib/sequel/database/misc.rb +6 -0
  30. data/lib/sequel/database/query.rb +33 -3
  31. data/lib/sequel/database/schema_methods.rb +6 -2
  32. data/lib/sequel/dataset/features.rb +6 -0
  33. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  34. data/lib/sequel/dataset/query.rb +17 -0
  35. data/lib/sequel/dataset/sql.rb +2 -53
  36. data/lib/sequel/exceptions.rb +4 -0
  37. data/lib/sequel/extensions/to_dot.rb +95 -83
  38. data/lib/sequel/model.rb +5 -0
  39. data/lib/sequel/model/associations.rb +80 -14
  40. data/lib/sequel/model/base.rb +182 -55
  41. data/lib/sequel/model/exceptions.rb +3 -1
  42. data/lib/sequel/plugins/association_pks.rb +6 -4
  43. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  44. data/lib/sequel/plugins/many_through_many.rb +8 -3
  45. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  46. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  47. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  48. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  49. data/lib/sequel/sql.rb +8 -0
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +43 -18
  52. data/spec/core/connection_pool_spec.rb +56 -77
  53. data/spec/core/database_spec.rb +25 -0
  54. data/spec/core/dataset_spec.rb +127 -16
  55. data/spec/core/expression_filters_spec.rb +13 -0
  56. data/spec/core/schema_spec.rb +6 -1
  57. data/spec/extensions/association_pks_spec.rb +7 -0
  58. data/spec/extensions/defaults_setter_spec.rb +64 -0
  59. data/spec/extensions/many_through_many_spec.rb +60 -4
  60. data/spec/extensions/nested_attributes_spec.rb +1 -0
  61. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  62. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  63. data/spec/extensions/prepared_statements_spec.rb +72 -0
  64. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  65. data/spec/extensions/to_dot_spec.rb +3 -5
  66. data/spec/integration/associations_test.rb +155 -1
  67. data/spec/integration/dataset_test.rb +8 -1
  68. data/spec/integration/plugin_test.rb +119 -0
  69. data/spec/integration/prepared_statement_test.rb +72 -1
  70. data/spec/integration/schema_test.rb +66 -8
  71. data/spec/integration/transaction_test.rb +40 -0
  72. data/spec/model/associations_spec.rb +349 -8
  73. data/spec/model/base_spec.rb +59 -0
  74. data/spec/model/hooks_spec.rb +161 -0
  75. data/spec/model/record_spec.rb +24 -0
  76. metadata +21 -4
@@ -424,6 +424,19 @@ describe "Blockless Ruby Filters" do
424
424
  @d.l(~{:x => Sequel::SQLFALSE}).should == '(x IS NOT FALSE)'
425
425
  end
426
426
 
427
+ it "should support direct negation of SQL::Constants" do
428
+ @d.l({:x => ~Sequel::NULL}).should == '(x IS NOT NULL)'
429
+ @d.l({:x => ~Sequel::NOTNULL}).should == '(x IS NULL)'
430
+ @d.l({:x => ~Sequel::TRUE}).should == '(x IS FALSE)'
431
+ @d.l({:x => ~Sequel::FALSE}).should == '(x IS TRUE)'
432
+ @d.l({:x => ~Sequel::SQLTRUE}).should == '(x IS FALSE)'
433
+ @d.l({:x => ~Sequel::SQLFALSE}).should == '(x IS TRUE)'
434
+ end
435
+
436
+ it "should raise an error if trying to invert an invalid SQL::Constant" do
437
+ proc{~Sequel::CURRENT_DATE}.should raise_error(Sequel::Error)
438
+ end
439
+
427
440
  it "should raise an error if trying to create an invalid complex expression" do
428
441
  proc{Sequel::SQL::ComplexExpression.new(:BANG, 1, 2)}.should raise_error(Sequel::Error)
429
442
  end
@@ -577,6 +577,12 @@ describe "DB#create_table?" do
577
577
  @db.create_table?(:cats){|*a|}
578
578
  @db.sqls.should == ['CREATE TABLE cats ()']
579
579
  end
580
+
581
+ specify "should use IF NOT EXISTS if the database supports that" do
582
+ @db.meta_def(:supports_create_table_if_not_exists?){true}
583
+ @db.create_table?(:cats){|*a|}
584
+ @db.sqls.should == ['CREATE TABLE IF NOT EXISTS cats ()']
585
+ end
580
586
  end
581
587
 
582
588
  describe "DB#drop_table" do
@@ -836,7 +842,6 @@ describe "Schema Parser" do
836
842
  @db.schema(:"time with time zone").first.last[:type].should == :time
837
843
  @db.schema(:"time without time zone").first.last[:type].should == :time
838
844
  @db.schema(:boolean).first.last[:type].should == :boolean
839
- @db.schema(:bit).first.last[:type].should == :boolean
840
845
  @db.schema(:real).first.last[:type].should == :float
841
846
  @db.schema(:float).first.last[:type].should == :float
842
847
  @db.schema(:double).first.last[:type].should == :float
@@ -57,6 +57,13 @@ describe "Sequel::Plugins::AssociationPks" do
57
57
  "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (1, 2)))"]
58
58
  end
59
59
 
60
+ specify "should use associated class's primary key for a one_to_many association" do
61
+ @Album.set_primary_key :foo
62
+ @Artist.load(:id=>1).album_pks = [1, 2]
63
+ @db.sqls.should == ["UPDATE albums SET artist_id = 1 WHERE (foo IN (1, 2))",
64
+ "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (foo NOT IN (1, 2)))"]
65
+ end
66
+
60
67
  specify "should set associated pks correctly for a many_to_many association" do
61
68
  @Album.load(:id=>2).tag_pks = [1, 3]
62
69
  @db.sqls[0].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 3)))"
@@ -0,0 +1,64 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::DefaultsSetter" do
4
+ before do
5
+ @db = db = Sequel::Database.new
6
+ @c = c = Class.new(Sequel::Model(db[:foo]))
7
+ @c.instance_variable_set(:@db_schema, {:a=>{}})
8
+ @c.plugin :defaults_setter
9
+ @c.columns :a
10
+ @pr = proc{|x| db.meta_def(:schema){|*| [[:a, {:ruby_default => x}]]}; c.dataset = c.dataset; c}
11
+ end
12
+
13
+ it "should set default value upon initialization" do
14
+ @pr.call(2).new.values.should == {:a=>2}
15
+ end
16
+
17
+ it "should not mark the column as modified" do
18
+ @pr.call(2).new.changed_columns.should == []
19
+ end
20
+
21
+ it "should not set a default of nil" do
22
+ @pr.call(nil).new.values.should == {}
23
+ end
24
+
25
+ it "should not override a given value" do
26
+ @pr.call(2)
27
+ @c.new('a'=>3).values.should == {:a=>3}
28
+ @c.new('a'=>nil).values.should == {:a=>nil}
29
+ end
30
+
31
+ it "should work correctly when subclassing" do
32
+ Class.new(@pr.call(2)).new.values.should == {:a=>2}
33
+ end
34
+
35
+ it "should contain the default values in default_values" do
36
+ @pr.call(2).default_values.should == {:a=>2}
37
+ @pr.call(nil).default_values.should == {}
38
+ end
39
+
40
+ it "should allow modifications of default values" do
41
+ @pr.call(2)
42
+ @c.default_values[:a] = 3
43
+ @c.new.values.should == {:a => 3}
44
+ end
45
+
46
+ it "should allow proc default values" do
47
+ @pr.call(2)
48
+ @c.default_values[:a] = proc{3}
49
+ @c.new.values.should == {:a => 3}
50
+ end
51
+
52
+ it "should have procs that set default values set them to nil" do
53
+ @pr.call(2)
54
+ @c.default_values[:a] = proc{nil}
55
+ @c.new.values.should == {:a => nil}
56
+ end
57
+
58
+ it "should work correctly on a model without a dataset" do
59
+ @pr.call(2)
60
+ c = Class.new(Sequel::Model(@db[:bar]))
61
+ c.plugin :defaults_setter
62
+ c.default_values.should == {:a=>2}
63
+ end
64
+ end
@@ -114,22 +114,78 @@ describe Sequel::Model, "many_through_many" do
114
114
 
115
115
  it "should allowing filtering by many_through_many associations" do
116
116
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
117
- @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE (albums_tags.tag_id = 1234)))'
117
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id = 1234) AND (albums_artists.artist_id IS NOT NULL))))'
118
118
  end
119
119
 
120
120
  it "should allowing filtering by many_through_many associations with a single through table" do
121
121
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id]]
122
- @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists WHERE (albums_artists.album_id = 1234)))'
122
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists WHERE ((albums_artists.album_id = 1234) AND (albums_artists.artist_id IS NOT NULL))))'
123
123
  end
124
124
 
125
125
  it "should allowing filtering by many_through_many associations with aliased tables" do
126
126
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums_artists, :id, :id], [:albums_artists, :album_id, :tag_id]]
127
- @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.id = albums_artists.album_id) INNER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.album_id = albums_artists_0.id) WHERE (albums_artists_1.tag_id = 1234)))'
127
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.id = albums_artists.album_id) INNER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.album_id = albums_artists_0.id) WHERE ((albums_artists_1.tag_id = 1234) AND (albums_artists.artist_id IS NOT NULL))))'
128
128
  end
129
129
 
130
130
  it "should allowing filtering by many_through_many associations with composite keys" do
131
131
  @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
132
- @c1.filter(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85))))'
132
+ @c1.filter(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL))))'
133
+ end
134
+
135
+ it "should allowing excluding by many_through_many associations" do
136
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
137
+ @c1.exclude(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE ((id NOT IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id = 1234) AND (albums_artists.artist_id IS NOT NULL)))) OR (id IS NULL))'
138
+ end
139
+
140
+ it "should allowing excluding by many_through_many associations with composite keys" do
141
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
142
+ @c1.exclude(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE (((id, yyy) NOT IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL)))) OR (id IS NULL) OR (yyy IS NULL))'
143
+ end
144
+
145
+ it "should allowing filtering by multiple many_through_many associations" do
146
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
147
+ @c1.filter(:tags=>[@c2.load(:id=>1234), @c2.load(:id=>2345)]).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (1234, 2345)) AND (albums_artists.artist_id IS NOT NULL))))'
148
+ end
149
+
150
+ it "should allowing filtering by multiple many_through_many associations with composite keys" do
151
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
152
+ @c1.filter(:tags=>[@c2.load(:h1=>1234, :h2=>85), @c2.load(:h1=>2345, :h2=>95)]).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN ((1234, 85), (2345, 95))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL))))'
153
+ end
154
+
155
+ it "should allowing excluding by multiple many_through_many associations" do
156
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
157
+ @c1.exclude(:tags=>[@c2.load(:id=>1234), @c2.load(:id=>2345)]).sql.should == 'SELECT * FROM artists WHERE ((id NOT IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (1234, 2345)) AND (albums_artists.artist_id IS NOT NULL)))) OR (id IS NULL))'
158
+ end
159
+
160
+ it "should allowing excluding by multiple many_through_many associations with composite keys" do
161
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
162
+ @c1.exclude(:tags=>[@c2.load(:h1=>1234, :h2=>85), @c2.load(:h1=>2345, :h2=>95)]).sql.should == 'SELECT * FROM artists WHERE (((id, yyy) NOT IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN ((1234, 85), (2345, 95))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL)))) OR (id IS NULL) OR (yyy IS NULL))'
163
+ end
164
+
165
+ it "should allowing filtering/excluding many_through_many associations with NULL values" do
166
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
167
+ @c1.filter(:tags=>@c2.new).sql.should == 'SELECT * FROM artists WHERE \'f\''
168
+ @c1.exclude(:tags=>@c2.new).sql.should == 'SELECT * FROM artists WHERE \'t\''
169
+ end
170
+
171
+ it "should allowing filtering by many_through_many association datasets" do
172
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
173
+ @c1.filter(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (SELECT id FROM tags WHERE ((x = 1) AND (id IS NOT NULL)))) AND (albums_artists.artist_id IS NOT NULL))))'
174
+ end
175
+
176
+ it "should allowing filtering by many_through_many association datasets with composite keys" do
177
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
178
+ @c1.filter(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN (SELECT h1, h2 FROM tags WHERE ((x = 1) AND (h1 IS NOT NULL) AND (h2 IS NOT NULL)))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL))))'
179
+ end
180
+
181
+ it "should allowing excluding by many_through_many association datasets" do
182
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
183
+ @c1.exclude(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE ((id NOT IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (SELECT id FROM tags WHERE ((x = 1) AND (id IS NOT NULL)))) AND (albums_artists.artist_id IS NOT NULL)))) OR (id IS NULL))'
184
+ end
185
+
186
+ it "should allowing excluding by many_through_many association datasets with composite keys" do
187
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
188
+ @c1.exclude(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE (((id, yyy) NOT IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN (SELECT h1, h2 FROM tags WHERE ((x = 1) AND (h1 IS NOT NULL) AND (h2 IS NOT NULL)))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL)))) OR (id IS NULL) OR (yyy IS NULL))'
133
189
  end
134
190
 
135
191
  it "should support a :conditions option" do
@@ -13,6 +13,7 @@ describe "NestedAttributes plugin" do
13
13
  mods << [:i, first_source, h, x]
14
14
  x
15
15
  end
16
+ define_method(:supports_insert_select?){true}
16
17
  define_method(:insert_select) do |h|
17
18
  x = ii.call
18
19
  mods << [:is, first_source, h, x]
@@ -0,0 +1,126 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::AssociationPks" do
4
+ before do
5
+ @db = MODEL_DB.clone
6
+ mod = Module.new do
7
+ def fetch_rows(sql)
8
+ db << (is_a?(Sequel::Dataset::PreparedStatementMethods) ? :prepared : :regular)
9
+ db << sql
10
+ end
11
+ end
12
+ @db.meta_def(:dataset) do |*opts|
13
+ ds = super(*opts)
14
+ ds.extend mod
15
+ ds
16
+ end
17
+ def @db.transaction(opts)
18
+ execute('BEGIN')
19
+ yield
20
+ execute('COMMIT')
21
+ end
22
+ @Artist = Class.new(Sequel::Model(@db[:artists]))
23
+ @Artist.columns :id, :id2
24
+ @Album= Class.new(Sequel::Model(@db[:albums]))
25
+ @Album.columns :id, :artist_id, :id2, :artist_id2
26
+ @Tag = Class.new(Sequel::Model(@db[:tags]))
27
+ @Tag.columns :id, :id2
28
+ @Artist.plugin :prepared_statements_associations
29
+ @Album.plugin :prepared_statements_associations
30
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
31
+ @Album.many_to_one :artist, :class=>@Artist
32
+ @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>:album_id
33
+ @Artist.plugin :many_through_many
34
+ @Artist.many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]], :class=>@Tag
35
+ @db.reset
36
+ end
37
+
38
+ specify "should run correct SQL for associations" do
39
+ @Artist.load(:id=>1).albums
40
+ @db.sqls.should == [:prepared, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
41
+ @db.reset
42
+
43
+ @Album.load(:id=>1, :artist_id=>2).artist
44
+ @db.sqls.should == [:prepared, "SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1"]
45
+ @db.reset
46
+
47
+ @Album.load(:id=>1, :artist_id=>2).tags
48
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1))"]
49
+ @db.reset
50
+
51
+ @Artist.load(:id=>1).tags
52
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.artist_id = 1))"]
53
+ end
54
+
55
+ specify "should run correct SQL for composite key associations" do
56
+ @Artist.one_to_many :albums, :class=>@Album, :key=>[:artist_id, :artist_id2], :primary_key=>[:id, :id2]
57
+ @Album.many_to_one :artist, :class=>@Artist, :key=>[:artist_id, :artist_id2], :primary_key=>[:id, :id2]
58
+ @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>[:album_id, :album_id2], :right_key=>[:tag_id, :tag_id2], :right_primary_key=>[:id, :id2], :left_primary_key=>[:id, :id2]
59
+ @Artist.many_through_many :tags, [[:albums, [:artist_id, :artist_id2], [:id, :id2]], [:albums_tags, [:album_id, :album_id2], [:tag_id, :tag_id2]]], :class=>@Tag, :right_primary_key=>[:id, :id2], :left_primary_key=>[:id, :id2]
60
+
61
+ @Artist.load(:id=>1, :id2=>2).albums
62
+ @db.sqls.should == [:prepared, "SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2))"]
63
+ @db.reset
64
+
65
+ @Album.load(:id=>1, :artist_id=>2, :artist_id2=>3).artist
66
+ @db.sqls.should == [:prepared, "SELECT * FROM artists WHERE ((artists.id = 2) AND (artists.id2 = 3)) LIMIT 1"]
67
+ @db.reset
68
+
69
+ @Album.load(:id=>1, :artist_id=>2, :id2=>3).tags
70
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2) AND (albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3))"]
71
+ @db.reset
72
+
73
+ @Artist.load(:id=>1, :id2=>2).tags
74
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2) AND (albums.artist_id = 1) AND (albums.artist_id2 = 2))"]
75
+ end
76
+
77
+ specify "should not run query if no objects can be associated" do
78
+ @Artist.new.albums.should == []
79
+ @Album.new.artist.should == nil
80
+ @db.sqls.should == []
81
+ end
82
+
83
+ specify "should run a regular query if there is a callback" do
84
+ @Artist.load(:id=>1).albums(proc{|ds| ds})
85
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
86
+ end
87
+
88
+ specify "should run a regular query if :prepared_statement=>false option is used for the association" do
89
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :prepared_statement=>false
90
+ @Artist.load(:id=>1).albums
91
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
92
+ end
93
+
94
+ specify "should run a regular query if unrecognized association is used" do
95
+ a = @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
96
+ a[:type] = :foo
97
+ @Artist.load(:id=>1).albums
98
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
99
+ end
100
+
101
+ specify "should run a regular query if a block is used when defining the association" do
102
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id do |ds| ds end
103
+ @Artist.load(:id=>1).albums
104
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
105
+ end
106
+
107
+ specify "should run a regular query if :conditions option is used when defining the association" do
108
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>1}
109
+ @Artist.load(:id=>1).albums
110
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (a = 1))"]
111
+ end
112
+
113
+ specify "should run a regular query if :dataset option is used when defining the association" do
114
+ album = @Album
115
+ @Artist.one_to_many :albums, :class=>@Album, :dataset=>proc{album.filter(:artist_id=>id)}
116
+ @Artist.load(:id=>1).albums
117
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (artist_id = 1)"]
118
+ end
119
+
120
+ specify "should run a regular query if :cloning an association that doesn't used prepared statements" do
121
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>1}
122
+ @Artist.one_to_many :oalbums, :clone=>:albums
123
+ @Artist.load(:id=>1).oalbums
124
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (a = 1))"]
125
+ end
126
+ end
@@ -0,0 +1,69 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "prepared_statements_safe plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:people))
6
+ @c.columns :id, :name, :i
7
+ @ds = ds = @c.dataset
8
+ @c.instance_variable_set(:@db_schema, {:i=>{}, :name=>{}, :id=>{:primary_key=>true}})
9
+ def ds.fetch_rows(sql)
10
+ db << {:server=>@opts[:server] || :read_only}.merge(opts)[:server]
11
+ super{|h|}
12
+ yield(:id=>1, :name=>'foo', :i=>2)
13
+ end
14
+ def ds.update(*)
15
+ db << default_server_opts(opts)[:server]
16
+ super
17
+ end
18
+ def ds.insert(*)
19
+ db << default_server_opts(opts)[:server]
20
+ super
21
+ 1
22
+ end
23
+ @c.plugin :prepared_statements_safe
24
+ @p = @c.load(:id=>1, :name=>'foo', :i=>2)
25
+ @c.db.execute 'foo'
26
+ @sqls = @c.db.sqls
27
+ @sqls.clear
28
+ end
29
+
30
+ specify "should load the prepared_statements plugin" do
31
+ @c.plugins.should include(Sequel::Plugins::PreparedStatements)
32
+ end
33
+
34
+ specify "should set default values correctly" do
35
+ @c.prepared_statements_column_defaults.should == {:name=>nil, :i=>nil}
36
+ @c.instance_variable_set(:@db_schema, {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
37
+ Class.new(@c).prepared_statements_column_defaults.should == {:name=>'foo'}
38
+ end
39
+
40
+ specify "should set default values when creating" do
41
+ @c.create
42
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \(NULL, NULL\)/
43
+ @sqls.clear
44
+ @c.create(:name=>'foo')
45
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \((NULL|'foo'), (NULL|'foo')\)/
46
+ @sqls.clear
47
+ @c.create(:name=>'foo', :i=>2)
48
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \((2|'foo'), (2|'foo')\)/
49
+ end
50
+
51
+ specify "should use database default values" do
52
+ @c.instance_variable_set(:@db_schema, {:i=>{:ruby_default=>2}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
53
+ c = Class.new(@c)
54
+ c.create
55
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \((2|'foo'), (2|'foo')\)/
56
+ end
57
+
58
+ specify "should not set defaults for unparseable dataset default values" do
59
+ @c.instance_variable_set(:@db_schema, {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
60
+ c = Class.new(@c)
61
+ c.create
62
+ @sqls[1].should == "INSERT INTO people (name) VALUES ('foo')"
63
+ end
64
+
65
+ specify "should save all fields when updating" do
66
+ @p.update(:i=>3)
67
+ @sqls[1].should =~ /UPDATE people SET (name = 'foo'|i = 3), (name = 'foo'|i = 3) WHERE \(id = 1\)/
68
+ end
69
+ end
@@ -0,0 +1,72 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "prepared_statements plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:people))
6
+ @c.columns :id, :name, :i
7
+ @ds = ds = @c.dataset
8
+ def ds.fetch_rows(sql)
9
+ db << {:server=>@opts[:server] || :read_only}.merge(opts)[:server]
10
+ super{|h|}
11
+ yield(:id=>1, :name=>'foo', :i=>2)
12
+ end
13
+ def ds.update(*)
14
+ db << default_server_opts(opts)[:server]
15
+ super
16
+ end
17
+ def ds.insert(*)
18
+ db << default_server_opts(opts)[:server]
19
+ super
20
+ 1
21
+ end
22
+ def ds.delete(*)
23
+ db << default_server_opts(opts)[:server]
24
+ super
25
+ end
26
+ @c.plugin :prepared_statements
27
+ @p = @c.load(:id=>1, :name=>'foo', :i=>2)
28
+ @c.db.execute 'foo'
29
+ @sqls = @c.db.sqls
30
+ @sqls.clear
31
+ end
32
+
33
+ specify "should correctly lookup by primary key" do
34
+ @c[1].should == @p
35
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE (id = 1) LIMIT 1"]
36
+ end
37
+
38
+ specify "should correctly delete instance" do
39
+ @p.destroy.should == @p
40
+ @sqls.should == [:default, "DELETE FROM people WHERE (id = 1)"]
41
+ end
42
+
43
+ specify "should correctly update instance" do
44
+ @p.update(:name=>'bar').should == @c.load(:id=>1, :name=>'bar', :i => 2)
45
+ @sqls.should == [:default, "UPDATE people SET name = 'bar' WHERE (id = 1)"]
46
+ end
47
+
48
+ specify "should correctly create instance" do
49
+ @c.create(:name=>'foo').should == @c.load(:id=>1, :name=>'foo', :i => 2)
50
+ @sqls.should == [:default, "INSERT INTO people (name) VALUES ('foo')", :default, "SELECT * FROM people WHERE (id = 1) LIMIT 1"]
51
+ end
52
+
53
+ specify "should correctly create instance if dataset supports insert_select" do
54
+ def @ds.supports_insert_select?
55
+ true
56
+ end
57
+ def @ds.insert_select(h)
58
+ return {:id=>1, :name=>'foo', :i => 2}
59
+ end
60
+ def @ds.insert_sql(*)
61
+ "#{super}#{' RETURNING *' if opts.has_key?(:returning)}"
62
+ end
63
+ @c.create(:name=>'foo').should == @c.load(:id=>1, :name=>'foo', :i => 2)
64
+ @sqls.should == [:default, "INSERT INTO people (name) VALUES ('foo') RETURNING *"]
65
+ end
66
+
67
+ specify "should work correctly when subclassing" do
68
+ c = Class.new(@c)
69
+ c[1].should == c.load(:id=>1, :name=>'foo', :i=>2)
70
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE (id = 1) LIMIT 1"]
71
+ end
72
+ end