sequel 3.23.0 → 3.24.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.
- 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
|