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.
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