sequel 3.23.0 → 3.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- 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
|
data/spec/core/schema_spec.rb
CHANGED
@@ -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
|
@@ -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
|