sequel 3.21.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 (104) hide show
  1. data/CHANGELOG +112 -0
  2. data/README.rdoc +15 -1
  3. data/doc/association_basics.rdoc +159 -40
  4. data/doc/model_hooks.rdoc +64 -27
  5. data/doc/prepared_statements.rdoc +8 -4
  6. data/doc/reflection.rdoc +8 -2
  7. data/doc/release_notes/3.22.0.txt +39 -0
  8. data/doc/release_notes/3.23.0.txt +172 -0
  9. data/doc/release_notes/3.24.0.txt +420 -0
  10. data/doc/virtual_rows.rdoc +2 -2
  11. data/lib/sequel/adapters/ado.rb +2 -1
  12. data/lib/sequel/adapters/db2.rb +8 -1
  13. data/lib/sequel/adapters/firebird.rb +25 -9
  14. data/lib/sequel/adapters/informix.rb +4 -19
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  17. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  18. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  19. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  20. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  21. data/lib/sequel/adapters/jdbc.rb +39 -20
  22. data/lib/sequel/adapters/odbc.rb +2 -0
  23. data/lib/sequel/adapters/oracle.rb +12 -0
  24. data/lib/sequel/adapters/postgres.rb +30 -1
  25. data/lib/sequel/adapters/shared/access.rb +10 -0
  26. data/lib/sequel/adapters/shared/informix.rb +45 -0
  27. data/lib/sequel/adapters/shared/mssql.rb +106 -11
  28. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  29. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  30. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  31. data/lib/sequel/adapters/sqlite.rb +8 -3
  32. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  33. data/lib/sequel/adapters/tinytds.rb +4 -3
  34. data/lib/sequel/ast_transformer.rb +190 -0
  35. data/lib/sequel/core.rb +1 -1
  36. data/lib/sequel/database/connecting.rb +1 -1
  37. data/lib/sequel/database/misc.rb +6 -0
  38. data/lib/sequel/database/query.rb +33 -3
  39. data/lib/sequel/database/schema_methods.rb +13 -4
  40. data/lib/sequel/dataset/features.rb +6 -0
  41. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  42. data/lib/sequel/dataset/query.rb +17 -0
  43. data/lib/sequel/dataset/sql.rb +2 -53
  44. data/lib/sequel/exceptions.rb +4 -0
  45. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  46. data/lib/sequel/extensions/migration.rb +4 -3
  47. data/lib/sequel/extensions/to_dot.rb +95 -83
  48. data/lib/sequel/model/associations.rb +234 -32
  49. data/lib/sequel/model/base.rb +187 -60
  50. data/lib/sequel/model/exceptions.rb +3 -1
  51. data/lib/sequel/model.rb +5 -0
  52. data/lib/sequel/plugins/association_pks.rb +6 -4
  53. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  54. data/lib/sequel/plugins/identity_map.rb +2 -2
  55. data/lib/sequel/plugins/many_through_many.rb +33 -3
  56. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  57. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  58. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  59. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  60. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  61. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  62. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  63. data/lib/sequel/sql.rb +8 -0
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +36 -0
  66. data/spec/adapters/mysql_spec.rb +6 -0
  67. data/spec/adapters/postgres_spec.rb +43 -18
  68. data/spec/adapters/spec_helper.rb +5 -0
  69. data/spec/core/connection_pool_spec.rb +56 -77
  70. data/spec/core/database_spec.rb +33 -0
  71. data/spec/core/dataset_spec.rb +127 -16
  72. data/spec/core/expression_filters_spec.rb +13 -0
  73. data/spec/core/schema_spec.rb +13 -1
  74. data/spec/core/spec_helper.rb +5 -0
  75. data/spec/extensions/association_pks_spec.rb +7 -0
  76. data/spec/extensions/columns_introspection_spec.rb +91 -0
  77. data/spec/extensions/defaults_setter_spec.rb +64 -0
  78. data/spec/extensions/many_through_many_spec.rb +77 -0
  79. data/spec/extensions/migration_spec.rb +17 -17
  80. data/spec/extensions/nested_attributes_spec.rb +1 -0
  81. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  82. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  83. data/spec/extensions/prepared_statements_spec.rb +72 -0
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  85. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  86. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  87. data/spec/extensions/spec_helper.rb +3 -1
  88. data/spec/extensions/to_dot_spec.rb +3 -5
  89. data/spec/extensions/xml_serializer_spec.rb +12 -0
  90. data/spec/integration/associations_test.rb +212 -0
  91. data/spec/integration/dataset_test.rb +8 -1
  92. data/spec/integration/plugin_test.rb +134 -0
  93. data/spec/integration/prepared_statement_test.rb +72 -1
  94. data/spec/integration/schema_test.rb +66 -8
  95. data/spec/integration/spec_helper.rb +5 -0
  96. data/spec/integration/transaction_test.rb +40 -0
  97. data/spec/integration/type_test.rb +7 -0
  98. data/spec/model/associations_spec.rb +463 -5
  99. data/spec/model/base_spec.rb +59 -0
  100. data/spec/model/eager_loading_spec.rb +269 -1
  101. data/spec/model/hooks_spec.rb +161 -0
  102. data/spec/model/record_spec.rb +30 -0
  103. data/spec/model/spec_helper.rb +5 -0
  104. metadata +29 -4
@@ -0,0 +1,91 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
+
3
+ describe "Sequel::Dataset.introspect_all_columns" do
4
+ before do
5
+ @db = MODEL_DB
6
+ @ds = @db[:a]
7
+ class Sequel::Dataset
8
+ # Handle case where introspect_all_columns has already been called
9
+ alias columns columns_without_introspection unless instance_methods(false).map{|x| x.to_s}.include?('columns')
10
+ end
11
+ Sequel::Dataset.introspect_all_columns
12
+ @db.reset
13
+ end
14
+ after do
15
+ class Sequel::Dataset
16
+ alias columns columns_without_introspection
17
+ end
18
+ end
19
+
20
+ specify "should turn on column introspection by default" do
21
+ @ds.select(:x).columns.should == [:x]
22
+ @db.sqls.length.should == 0
23
+ end
24
+ end
25
+
26
+ describe "columns_introspection extension" do
27
+ before do
28
+ @db = MODEL_DB
29
+ @ds = @db[:a]
30
+ @ds.extend(Sequel::ColumnsIntrospection.dup) # dup to allow multiple places in class hierarchy
31
+ @db.reset
32
+ end
33
+
34
+ specify "should not issue a database query if the columns are already loaded" do
35
+ @ds.instance_variable_set(:@columns, [:x])
36
+ @ds.columns.should == [:x]
37
+ @db.sqls.length.should == 0
38
+ end
39
+
40
+ specify "should handle plain symbols without a database query" do
41
+ @ds.select(:x).columns.should == [:x]
42
+ @db.sqls.length.should == 0
43
+ end
44
+
45
+ specify "should handle qualified symbols without a database query" do
46
+ @ds.select(:t__x).columns.should == [:x]
47
+ @db.sqls.length.should == 0
48
+ end
49
+
50
+ specify "should handle aliased symbols without a database query" do
51
+ @ds.select(:x___a).columns.should == [:a]
52
+ @db.sqls.length.should == 0
53
+ end
54
+
55
+ specify "should handle qualified and aliased symbols without a database query" do
56
+ @ds.select(:t__x___a).columns.should == [:a]
57
+ @db.sqls.length.should == 0
58
+ end
59
+
60
+ specify "should handle SQL::Identifiers " do
61
+ @ds.select(:x.identifier).columns.should == [:x]
62
+ @db.sqls.length.should == 0
63
+ end
64
+
65
+ specify "should handle SQL::QualifiedIdentifiers" do
66
+ @ds.select(:x.qualify(:t)).columns.should == [:x]
67
+ @ds.select(:x.identifier.qualify(:t)).columns.should == [:x]
68
+ @db.sqls.length.should == 0
69
+ end
70
+
71
+ specify "should handle SQL::AliasedExpressions" do
72
+ @ds.select(:x.as(:a)).columns.should == [:a]
73
+ @ds.select(:x.as(:a.identifier)).columns.should == [:a]
74
+ @db.sqls.length.should == 0
75
+ end
76
+
77
+ specify "should issue a database query if the wildcard is selected" do
78
+ @ds.columns
79
+ @db.sqls.length.should == 1
80
+ end
81
+
82
+ specify "should issue a database query if an unsupported type is used" do
83
+ @ds.select(1).columns
84
+ @db.sqls.length.should == 1
85
+ end
86
+
87
+ specify "should not have column introspection on by default" do
88
+ @db[:a].select(:x).columns
89
+ @db.sqls.length.should == 1
90
+ end
91
+ end
@@ -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
@@ -8,6 +8,7 @@ describe Sequel::Model, "many_through_many" do
8
8
  plugin :many_through_many
9
9
  end
10
10
  class ::Tag < Sequel::Model
11
+ columns :id, :h1, :h2
11
12
  end
12
13
  MODEL_DB.reset
13
14
  @c1 = Artist
@@ -111,6 +112,82 @@ describe Sequel::Model, "many_through_many" do
111
112
  n.tags.should == [@c2.load(:id=>1)]
112
113
  end
113
114
 
115
+ it "should allowing filtering by many_through_many associations" do
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) AND (albums_artists.artist_id IS NOT NULL))))'
118
+ end
119
+
120
+ it "should allowing filtering by many_through_many associations with a single through table" do
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) AND (albums_artists.artist_id IS NOT NULL))))'
123
+ end
124
+
125
+ it "should allowing filtering by many_through_many associations with aliased tables" do
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) AND (albums_artists.artist_id IS NOT NULL))))'
128
+ end
129
+
130
+ it "should allowing filtering by many_through_many associations with composite keys" do
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) 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))'
189
+ end
190
+
114
191
  it "should support a :conditions option" do
115
192
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
116
193
  n = @c1.load(:id => 1234)
@@ -202,7 +202,7 @@ describe "Sequel::IntegerMigrator" do
202
202
  @drops = []
203
203
  @tables_created = []
204
204
  @columns_created = []
205
- @versions = {}
205
+ @versions = Hash.new{|h,k| h[k.to_sym]}
206
206
  end
207
207
 
208
208
  def version; versions.values.first || 0; end
@@ -212,7 +212,7 @@ describe "Sequel::IntegerMigrator" do
212
212
  def create_table(name, opts={}, &block)
213
213
  super
214
214
  @columns_created << / \(?(\w+) integer.*\)?\z/.match(sqls.last)[1].to_sym
215
- @tables_created << name
215
+ @tables_created << name.to_sym
216
216
  end
217
217
 
218
218
  def dataset(opts={})
@@ -228,7 +228,7 @@ describe "Sequel::IntegerMigrator" do
228
228
  end
229
229
 
230
230
  def table_exists?(name)
231
- @tables_created.include?(name)
231
+ @tables_created.include?(name.to_sym)
232
232
  end
233
233
  end
234
234
  @db = dbc.new
@@ -250,7 +250,7 @@ describe "Sequel::IntegerMigrator" do
250
250
 
251
251
  specify "should add a column name if it doesn't already exist in the schema_info table" do
252
252
  @db.create_table(:schema_info){Integer :v}
253
- @db.should_receive(:alter_table).with(:schema_info)
253
+ @db.should_receive(:alter_table).with('schema_info')
254
254
  Sequel::Migrator.apply(@db, @dirname)
255
255
  end
256
256
 
@@ -320,45 +320,45 @@ describe "Sequel::TimestampMigrator" do
320
320
  @dsc = dsc = Class.new(MockDataset) do
321
321
  def columns
322
322
  case opts[:from].first
323
- when :schema_info
323
+ when :schema_info, 'schema_info'
324
324
  [:version]
325
- when :schema_migrations
325
+ when :schema_migrations, 'schema_migrations'
326
326
  [:filename]
327
- when :sm
327
+ when :sm, 'sm'
328
328
  [:fn]
329
329
  end
330
330
  end
331
331
 
332
332
  def fetch_rows(sql)
333
333
  case opts[:from].first
334
- when :schema_info
334
+ when :schema_info, 'schema_info'
335
335
  yield({:version=>$sequel_migration_version})
336
- when :schema_migrations
336
+ when :schema_migrations, 'schema_migrations'
337
337
  $sequel_migration_files.sort.each{|f| yield(:filename=>f)}
338
- when :sm
338
+ when :sm, 'sm'
339
339
  $sequel_migration_files.sort.each{|f| yield(:fn=>f)}
340
340
  end
341
341
  end
342
342
 
343
343
  def insert(h={})
344
344
  case opts[:from].first
345
- when :schema_info
345
+ when :schema_info, 'schema_info'
346
346
  $sequel_migration_version = h.values.first
347
- when :schema_migrations, :sm
347
+ when :schema_migrations, :sm, 'schema_migrations', 'sm'
348
348
  $sequel_migration_files << h.values.first
349
349
  end
350
350
  end
351
351
 
352
352
  def update(h={})
353
353
  case opts[:from].first
354
- when :schema_info
354
+ when :schema_info, 'schema_info'
355
355
  $sequel_migration_version = h.values.first
356
356
  end
357
357
  end
358
358
 
359
359
  def delete
360
360
  case opts[:from].first
361
- when :schema_migrations, :sm
361
+ when :schema_migrations, :sm, 'schema_migrations', 'sm'
362
362
  $sequel_migration_files.delete(opts[:where].args.last)
363
363
  end
364
364
  end
@@ -366,9 +366,9 @@ describe "Sequel::TimestampMigrator" do
366
366
  dbc = Class.new(MockDatabase) do
367
367
  tables = {}
368
368
  define_method(:dataset){|*a| dsc.new(self, *a)}
369
- define_method(:create_table){|name, *args| tables[name] = true}
370
- define_method(:drop_table){|*names| names.each{|n| tables.delete(n)}}
371
- define_method(:table_exists?){|name| tables.has_key?(name)}
369
+ define_method(:create_table){|name, *args| tables[name.to_sym] = true}
370
+ define_method(:drop_table){|*names| names.each{|n| tables.delete(n.to_sym)}}
371
+ define_method(:table_exists?){|name| tables.has_key?(name.to_sym)}
372
372
  end
373
373
  @db = dbc.new
374
374
  @m = Sequel::Migrator
@@ -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
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "prepared_statements_with_pk 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
+ @c.plugin :prepared_statements_with_pk
14
+ @p = @c.load(:id=>1, :name=>'foo', :i=>2)
15
+ @c.db.execute 'foo'
16
+ @sqls = @c.db.sqls
17
+ @sqls.clear
18
+ end
19
+
20
+ specify "should load the prepared_statements plugin" do
21
+ @c.plugins.should include(Sequel::Plugins::PreparedStatements)
22
+ end
23
+
24
+ specify "should correctly lookup by primary key from dataset" do
25
+ @c.dataset.filter(:name=>'foo')[1].should == @p
26
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE ((name = 'foo') AND (id = 1)) LIMIT 1"]
27
+ end
28
+
29
+ specify "should still work correctly if there are multiple conflicting variables" do
30
+ @c.dataset.filter(:name=>'foo').or(:name=>'bar')[1].should == @p
31
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE (((name = 'foo') OR (name = 'bar')) AND (id = 1)) LIMIT 1"]
32
+ end
33
+
34
+ specify "should still work correctly if the primary key is used elsewhere in the query" do
35
+ @c.dataset.filter{id > 2}[1].should == @p
36
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE ((id > 2) AND (id = 1)) LIMIT 1"]
37
+ end
38
+ end